diff --git a/README.md b/README.md index 212d73f..6ccd2c7 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,19 @@ npm install -g openapi3-generator Options: - -V, --version output the version number - -o, --output directory where to put the generated files (defaults to current directory) - -t, --templates directory where templates are located (defaults to internal templates directory) - -b, --basedir directory to use as the base when resolving local file references (defaults to OpenAPI file directory) - -h, --help output usage information + -V, --version output the version number + -o, --output directory where to put the generated files (defaults to current directory) + -t, --templates directory where templates are located (defaults to internal templates directory) + -b, --basedir directory to use as the base when resolving local file references (defaults to OpenAPI file directory) + -c, --curl generate a curl scripts (defaults is false) + -s, --skipExistingFiles skip existing files + -d, --deleteFolders directory names to be deleted, e.g. "auto", "*" + -h, --help output usage information ``` #### Examples -The shortest possible syntax: +The shortest possible syntax to create markdowns for the openapi.yaml file: ```bash og openapi.yaml markdown ``` @@ -44,6 +47,21 @@ Specify where to put the generated code: og -o ./my-docs openapi.yaml markdown ``` +The syntax to create an express server with only the endpoints for the openapi.yaml file: +```bash +og -o ./express-server openapi.yaml express +``` + +The syntax to create an full functionaly CRUD-Server, which reads and writes from json-files for the openapi.yaml file: +```bash +og -o ./demo-crud-server openapi.yaml json_crud_server +``` + +The syntax to create an full functionaly CRUD-Server with own templates and deletion of the existing demo-server : +```bash +og -d * -o ./demo-server -t ./ openapi.yaml demo-server-templates +``` + ## Templates ### Creating your own templates @@ -55,6 +73,7 @@ The files in your template can be of the following types: 2. Templates: This kind of files will be compiled using [Handlebars](http://handlebarsjs.com/), and copied to the output directory. 3. Path templates: This kind of files will be compiled using [Handlebars](http://handlebarsjs.com/), but it will generate one file per OpenAPI path. +#### Example 1 - Express server Assuming we have the following OpenAPI Spec: ```yaml openapi: "3.0.0" @@ -109,6 +128,107 @@ In this example the generated directory structure will be like this: |+ user/ | - route.js // this file also contains the code for methods on users. ``` +#### Example 2 - Json CRUD server +Assuming we have the following OpenAPI Spec: +```yaml +openapi: 3.0.0 +info: + title: CRUD Service + version: 1.0.0 + description: CRUD Service Example. +servers: + - url: http://localhost:8080/crud-service/rest/v1 +... +paths: + ... + /variants: + get:... + post:... + /variants/{variantId}: + parameters: + - name: variantId ... + get:... + delete:... + put:... + /variants/{variantId}/phases: + parameters: + - name: variantId ... + get:... + post:... + ... +components: + ... + schemas: + .... + Variant: + description: The variant as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The variant's internal ID. + example: 123456 + name: + type: "string" + description: The variant's name. + example: "Variant" + creationInfo: + $ref: "#/components/schemas/CreationInfo" + modificationInfo: + $ref: "#/components/schemas/ModificationInfo" + objectVersionInfo: + $ref: "#/components/schemas/ObjectVersionInfo" + required: + - name + .... +``` +And some template files like this: +``` +|+ api/ + |- index.js.hbs // This is a static template, it contains placeholders that will be filled in, e.g. includes for each file in routes. + |+ constrains/ + |- $$schema$$.constrain.js.hbs // This file will be generated for each schema and contains standard functions to check constrains, each could be change. + |+ data/ + |- $$schema$$.data.json.hbs // This json file will be generated for each schema and will be filled with the example values of the schemas. + |+ helpers/ + |- errorResponse.js // This file will be static and contains the errorModel. + |- helper.js // This file will be static and contains the helper methods for the crud functionality to read, write and search in json files. + |+ models/ + |- $$schema$$.model.js.hbs // This file will be generated for each schema and includes the model for the schema and the mandatory field checks. + |+ routes/ + |- $$path$$.route.js.hbs // This file will be generated for each operation and contains skeleton code for each method for an operation. + |+ services/ + |- $$path$$.service.js.hbs // This file will be generated for each operation and contains the crud functionality code for each method for an operation. + +``` +The first important thing to notice here is the variable notation in `$$path$$.route.js.hbs`. It will be replaced by the name of the path. +The second important thing to notice here is the variable notation in `$$schema$$.model.js.hbs`. It will be replaced by the name of the schema. + + +In this example the generated directory structure will be like this: +``` +|+ api/ + |- index.js // This file will now e.g. have included the files in routes. + |+ constrains/ + |- variants.constrain.js // This file will be used to check additionaly constrains. It can be replaced by a static file with the same name. + ... + |+ data/ + |- variants.data.json // This json filecontains the example values for the Schama Variant and is used to store new variants and changes. + ... +|+ helpers/ + |- errorResponse.js // This file contains the errorModel. + |- helper.js // This file contains the helper methods for the crud functionality to read, write and search in json files. + |+ models/ + |- variants.model.js // This file contains the model for the schema Variants and the mandatory field checks to create or update a variant. + ... + |+ routes/ + |- variants.route.js // This file contains the code for methods on variants. + ... + |+ services/ + |- variants.service.js // This file contains the code for methods on variants to read write the data from variants.datajson. + ... +``` ### Template file extensions You can (optionally) name your template files with `.hbs` extensions, which will be removed when writing the generated @@ -158,3 +278,4 @@ Check out some examples in the [markdown](./templates/markdown/.partials) templa * Fran Méndez ([@fmvilas](http://twitter.com/fmvilas)) * Richard Klose ([@richardklose](http://github.com/richardklose)) +* Matthias Suessmeier ([@suessmma](https://github.com/suessmma)) diff --git a/lib/beautifier.js b/lib/beautifier.js index b5c2693..e920f6a 100644 --- a/lib/beautifier.js +++ b/lib/beautifier.js @@ -65,7 +65,7 @@ const beautifySchema = (schema) => { return schema; }; -const beautifyOperation = (operation, operationName, pathName, options) => { +const beautifyOperation = (operation, operationName, path, pathName, options) => { operation.slug = slugg(`op-${operationName}-${pathName}`); operation.summaryAsHTML = mdToHTML(operation.summary); operation.descriptionAsHTML = mdToHTML(operation.description); @@ -116,6 +116,19 @@ const beautifyOperation = (operation, operationName, pathName, options) => { }); } + operation.parameters = []; + _.each(path.parameters, param => { + operation.parameters.push(param); + if (param.name.toLowerCase() == path.pathIdParameter) { + operation.idparameter = param.name; + } + }); + if(operation.operationId == undefined){ + operation.operationId = _.camelCase(`${operationName}${pathName}`); + } + if(path.additionalData != undefined){ + operation.additionalData = path.additionalData; + } return operation; }; @@ -128,6 +141,20 @@ const cleanBrackets = text => { return finalText; }; +/** + * Accommodates Openapi object for additional operation with other objects for easier reading. + */ +const generateAdditionalOperation = (operationName, path, id) => { + const pathLower = path.toLowerCase(); + const opertationNameLower = "/" + operationName.toLowerCase(); + const idLower = '{' + id.toLowerCase() + '}'; + if (!pathLower.endsWith(opertationNameLower) && !pathLower.endsWith(idLower)) { + return path.substring(path.lastIndexOf('/')+1,path.length); + } else { + return null; + } +} + module.exports = (openapi, config) => { openapi.basePath = openapi.basePath || ''; openapi.info = openapi.info || {}; @@ -177,8 +204,21 @@ module.exports = (openapi, config) => { const httpMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND']; + + pathBasic = pathName === '/' ? 'root' : pathName.split('/')[1]; + let pathIdParameter = '' + if (pathBasic.endsWith('s')){ + pathIdParameter = _.camelCase(pathBasic.slice(0, -1).concat("Id")).toLowerCase(); + }else{ + pathIdParameter = _.camelCase(pathBasic.slice(0).concat("Id")).toLowerCase(); + } + path.pathIdParameter = pathIdParameter; + const additionalData = generateAdditionalOperation(path.endpointName, pathName, pathIdParameter); + if(additionalData !=null){ + path.additionalData = additionalData; + } _.each(path, (operation, operationName) => { - if (httpMethods.includes(operationName.toUpperCase())) beautifyOperation(operation, operationName, pathName, config); + if (httpMethods.includes(operationName.toUpperCase())) beautifyOperation(operation, operationName, path, pathName, config); }); }); @@ -186,7 +226,14 @@ module.exports = (openapi, config) => { const commonPrefix = sharedStart(Object.keys(openapi.paths)); const levels = commonPrefix.split('/').length - 1; - openapi.__commonPrefix = commonPrefix.split('/').slice(0, levels).join('/'); - + openapi.__commonPrefix = commonPrefix.split('/').slice(0, levels); + let baseServerPath = ''; + if (openapi.servers) { + baseServerPath = openapi.servers[0].url; + baseServerPath = baseServerPath.slice(baseServerPath.indexOf('/', 8), baseServerPath.length) + } + if (openapi.basePath == ''){ + openapi.basePath = baseServerPath; + } return openapi; }; diff --git a/lib/generator.js b/lib/generator.js index 5198a7d..bc4a8bc 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -22,7 +22,7 @@ const HELPERS_DIRNAME = '.helpers'; const PARTIALS_DIRNAME = '.partials'; /** - * Deletes all matching subfolders in target directory + * Deletes all matching subfolders in target directory * * @param {Object} config Configuration options * @returns {Promise} @@ -78,7 +78,7 @@ const generateFile = options => new Promise((resolve, reject) => { console.warn(yellow(`Skipping file: ${generated_path}`)); resolve(); } - + } catch (e) { reject(e); } @@ -97,8 +97,8 @@ const generateOperationFile = (config, operation, operation_name) => new Promise fs.readFile(path.join(config.root, config.file_name), 'utf8', (err, data) => { if (err) return reject(err); const subdir = config.root - .replace(new RegExp(`${config.templates_dir}[/]?`),'') - .replace("$$path$$", _.kebabCase(operation_name)); + .replace(config.templates_dir + "\\" ,'') + .replace("$$path$$", _.kebabCase(operation_name)); const new_filename = config.file_name.replace('$$path$$', operation_name).replace(/.hbs$/, ''); const target_file = path.resolve(config.target_dir, subdir, new_filename); @@ -112,9 +112,15 @@ const generateOperationFile = (config, operation, operation_name) => new Promise }); xfs.mkdirpSync(path.dirname(target_file)); - fs.writeFile(target_file, content, 'utf8', (err) => { - if (err) return reject(err); - resolve(); + fs.access(target_file, fs.F_OK, (err) => { + if (err) { + fs.writeFile(target_file, content, 'utf8', (err) => { + if (err) return reject(err); + resolve(); + }); + }else{ + resolve(); + } }); }); }); @@ -134,7 +140,6 @@ const generateOperationFiles = config => new Promise((resolve, reject) => { } path_name = path_name.replace(/}/g, '').replace(/{/g, ':'); - files[operation_name].push({ path_name, path, @@ -148,6 +153,64 @@ const generateOperationFiles = config => new Promise((resolve, reject) => { }); }); +/** + * Recursiv function to add all proporties of the schema with the name + * + * @param {Object} config Configuration options + * @returns {Promise} + */ + +function setPropertyName(property, name) { + property.name = name; + _.each(property.properties, (pro, pro_name) => { + setPropertyName(pro,pro_name ); + }); +} + +/** + * Generates all the files for each schema by iterating over the schemas. + * + * @param {Object} config Configuration options + * @returns {Promise} + */ +const generateSchemaFiles = config => new Promise((resolve, reject) => { + const files = {}; + _.each(config.data.openapi.components.schemas, (schema, schema_name) => { + _.each(schema.properties, (property, property_name) => { + setPropertyName(property, property_name); + }); + + fs.readFile(path.join(config.root, config.file_name), 'utf8', (err, data) => { + if (err) return reject(err); + const subdir = config.root + .replace(config.templates_dir + "\\", ''); + const new_schema_name = schema_name + "s" + const new_filename = config.file_name.replace('$$schema$$', _.camelCase(new_schema_name)).replace(/.hbs$/, ''); + const target_file = path.resolve(config.target_dir, subdir, new_filename); + const template = Handlebars.compile(data.toString()); + const content = template({ + openbrace: '{', + closebrace: '}', + schema_name: new_schema_name, + schema_properties: schema.properties, + schema_properties_required: schema.required, + openapi: config.data.openapi + }); + + xfs.mkdirpSync(path.dirname(target_file)); + fs.access(target_file, fs.F_OK, (err) => { + if (err) { + fs.writeFile(target_file, content, 'utf8', (err) => { + if (err) return reject(err); + resolve(); + }); + } else { + resolve(); + } + }); + }); + }); +}); /** * Generates the directory structure. * @@ -181,6 +244,17 @@ const generateDirectoryStructure = config => new Promise((resolve, reject) => { }); const template_path = path.relative(templates_dir, path.resolve(root, stats.name)); fs.unlink(path.resolve(target_dir, template_path), next); + }else if (stats.name.includes('$$schema$$') || root.includes("$$schema$$")) { + // this file should be handled for each in openapi.paths + await generateSchemaFiles({ + root, + templates_dir, + target_dir, + data: config, + file_name: stats.name + }); + const template_path = path.relative(templates_dir, path.resolve(root, stats.name)); + fs.unlink(path.resolve(target_dir, template_path), next); } else { const file_path = path.relative(templates_dir, path.resolve(root, stats.name)); if (!file_path.startsWith(`${PARTIALS_DIRNAME}${path.sep}`) && !file_path.startsWith(`${HELPERS_DIRNAME}${path.sep}`)) { diff --git a/package.json b/package.json index b87b113..1222ef6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,10 @@ { "name": "Richard Klose", "email": "richard.klose@gmail.com" + }, + { + "name": "Matthias Suessmeier", + "email": "suessmma94@gmail.com" } ], "homepage": "https://github.com/fmvilas/openapi3-generator", diff --git a/templates/json_crud_server/.editorconfig b/templates/json_crud_server/.editorconfig new file mode 100644 index 0000000..279aeed --- /dev/null +++ b/templates/json_crud_server/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/templates/json_crud_server/.eslintrc b/templates/json_crud_server/.eslintrc new file mode 100644 index 0000000..f9e47ce --- /dev/null +++ b/templates/json_crud_server/.eslintrc @@ -0,0 +1,79 @@ +extends: 'eslint:recommended' + +env: + node: true + es6: true + +parserOptions: + ecmaVersion: 2017 + +rules: + # Possible Errors + no-console: 0 + valid-jsdoc: [0, {requireReturn: false, requireParamDescription: false, requireReturnDescription: false}] + + # Best Practices + consistent-return: 0 + curly: 0 + block-scoped-var: 2 + no-else-return: 2 + no-process-env: 2 + no-self-compare: 2 + no-throw-literal: 2 + no-void: 2 + radix: 2 + wrap-iife: [2, outside] + + # Variables + no-shadow: 0 + no-use-before-define: [2, nofunc] + no-unused-vars: [2, { "argsIgnorePattern": "next" }] + + # Node.js + no-process-exit: 0 + handle-callback-err: [2, err] + no-new-require: 2 + no-path-concat: 2 + + # Stylistic Issues + quotes: [2, single] + camelcase: 0 + indent: [2, 2] + no-lonely-if: 2 + no-floating-decimal: 2 + brace-style: [2, 1tbs, { "allowSingleLine": true }] + comma-style: [2, last] + consistent-this: [0, self] + func-style: 0 + max-nested-callbacks: 0 + new-cap: [2, {capIsNewExceptions: [JID]}] + no-multiple-empty-lines: [2, {max: 1}] + no-nested-ternary: 2 + semi-spacing: [2, {before: false, after: true}] + operator-assignment: [2, always] + padded-blocks: [2, never] + quote-props: [2, as-needed] + space-before-function-paren: [2, always] + keyword-spacing: [2, {after: true}] + space-before-blocks: [2, always] + array-bracket-spacing: [2, never] + computed-property-spacing: [2, never] + space-in-parens: [2, never] + space-unary-ops: [2, {words: true, nonwords: false}] + #spaced-line-comment: [2, always] + wrap-regex: 2 + linebreak-style: [2, unix] + semi: [2, always] + + # ECMAScript 6 + arrow-spacing: [2, {before: true, after: true}] + no-class-assign: 2 + no-const-assign: 2 + no-dupe-class-members: 2 + no-this-before-super: 2 + no-var: 2 + object-shorthand: [2, always] + prefer-arrow-callback: 2 + prefer-const: 2 + prefer-spread: 2 + prefer-template: 2 diff --git a/templates/json_crud_server/.gitignore b/templates/json_crud_server/.gitignore new file mode 100644 index 0000000..825fc67 --- /dev/null +++ b/templates/json_crud_server/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +npm-debug.log diff --git a/templates/json_crud_server/.helpers/all.js b/templates/json_crud_server/.helpers/all.js new file mode 100644 index 0000000..43b223e --- /dev/null +++ b/templates/json_crud_server/.helpers/all.js @@ -0,0 +1,258 @@ +// Module constructor provides dependency injection from the generator instead of relying on require's cache here to ensure +// the same instance of Handlebars gets the helpers installed and Lodash is definitiely available +// regardless of where remote templates reside: in another Node project or a plain directory, which may have different or no modules available. +module.exports = (Handlebars, _) => { + + /** + * Compares two values. + */ + Handlebars.registerHelper('equal', (lvalue, rvalue, options) => { + if (arguments.length < 3) + throw new Error('Handlebars Helper equal needs 2 parameters'); + if (lvalue != rvalue) { + return options.inverse(this); + } + + return options.fn(this); + }); + + /** + * Checks if a string ends with a provided value. + */ + Handlebars.registerHelper('endsWith', (lvalue, rvalue, options) => { + if (arguments.length < 3) + throw new Error('Handlebars Helper equal needs 2 parameters'); + if (lvalue.lastIndexOf(rvalue) !== lvalue.length - 1 || lvalue.length - 1 < 0) { + return options.inverse(this); + } + return options.fn(this); + }); + + /** + * Checks if Value end with another value not case sensitive. + */ + Handlebars.registerHelper('endsWithLowerCase', (lvalue, rvalue, options) => { + if (arguments.length < 3) + throw new Error('Handlebars Helper match needs 2 parameters'); + if (lvalue != undefined && rvalue != undefined) { + const lvalueLowerCase = lvalue.toLowerCase(); + const rvalueLowerCase = rvalue.toLowerCase(); + if (!lvalueLowerCase.endsWith(rvalueLowerCase)) { + return options.inverse(this); + } + return options.fn(this); + } else { + return options.inverse(this); + } + }); + /** + * Checks if a method is a valid HTTP method. + */ + Handlebars.registerHelper('validMethod', (method, options) => { + const authorized_methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'COPY', 'HEAD', 'OPTIONS', 'LINK', 'UNLIK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND']; + + if (arguments.length < 3) + throw new Error('Handlebars Helper validMethod needs 1 parameter'); + if (authorized_methods.indexOf(method.toUpperCase()) === -1) { + return options.inverse(this); + } + + return options.fn(this); + }); + + /** + * Checks if a collection of responses contains no error responses. + */ + Handlebars.registerHelper('ifNoErrorResponses', (responses, options) => { + const codes = responses ? Object.keys(responses) : []; + if (codes.find(code => Number(code) >= 400)) return options.inverse(this); + + return options.fn(this); + }); + + /** + * Checks if a collection of responses contains no success responses. + */ + Handlebars.registerHelper('ifNoSuccessResponses', (responses, options) => { + const codes = responses ? Object.keys(responses) : []; + if (codes.find(code => Number(code) >= 200 && Number(code) < 300)) return options.inverse(this); + + return options.fn(this); + }); + + /** + * Checks if a string matches a RegExp. + */ + Handlebars.registerHelper('match', (lvalue, rvalue, options) => { + if (arguments.length < 3) + throw new Error('Handlebars Helper match needs 2 parameters'); + if (!lvalue.match(rvalue)) { + return options.inverse(this); + } + + return options.fn(this); + }); + /** + * Check if value exists + */ + Handlebars.registerHelper('NotUndefined', (value, options) => { + if (arguments.length < 2) + throw new Error('Handlebars Helper match needs 2 parameters'); + if (value != undefined) { + return options.fn(this); + } else { + return options.inverse(this); + } + }); + /** + * Provides different ways to compare two values (i.e. equal, greater than, different, etc.) + */ + Handlebars.registerHelper('compare', (lvalue, rvalue, options) => { + if (arguments.length < 3) throw new Error('Handlebars Helper "compare" needs 2 parameters'); + + const operator = options.hash.operator || '=='; + const operators = { + '==': (l, r) => { + return l == r; + }, + '===': (l, r) => { + return l === r; + }, + '!=': (l, r) => { + return l != r; + }, + '<': (l, r) => { + return l < r; + }, + '>': (l, r) => { + return l > r; + }, + '<=': (l, r) => { + return l <= r; + }, + '>=': (l, r) => { + return l >= r; + }, + typeof: (l, r) => { + return typeof l == r; + } + }; + + if (!operators[operator]) throw new Error(`Handlebars Helper 'compare' doesn't know the operator ${operator}`); + + const result = operators[operator](lvalue, rvalue); + + if (result) { + return options.fn(this); + } + + return options.inverse(this); + }); + + /** + * Capitalizes a string. + */ + Handlebars.registerHelper('capitalize', (str) => { + return _.capitalize(str); + }); + + /** + * Converts a string to its camel-cased version. + */ + Handlebars.registerHelper('camelCase', (str) => { + return _.camelCase(str); + }); + + /** + * Converts a multi-line string to a single line. + */ + Handlebars.registerHelper('inline', (str) => { + return str ? str.replace(/\n/g, '') : ''; + }); + + /** + * Quotes a JS identifier, if necessary. + */ + Handlebars.registerHelper('quote', (str) => { + return /[$&@-]/.test(str) ? `'${str}'` : str + }); + + /** + * Transform Proporty to valid json. + */ + Handlebars.registerHelper('transformToJsonData', (property) => { + return checkProperty(property); + }); + + /** + * Helper function to transform proporty to valid json. + */ + function checkProperty(property) { + if (property.type == "integer" || property.type == "boolean" || property.type == "number") { + return `"${property.name}": ${checkExampleValues(property.example, property.type)}`; + } else if (property.type == "object") { + let object = `"${property.name}": {`; + let i; + for (i = 0; i < _.values(property.properties).length; i++) { + object += checkProperty(_.values(property.properties)[i]); + if (i < _.values(property.properties).length - 1) { + object += `,`; + } + } + object += `}`; + return object; + } else if (property.type == "array") { + let object = `"${property.name}": [`; + let i; + if (property.example != undefined) { + for (i = 0; i < property.example.length; i++) { + object += checkExampleValues(property.example[i], property.items.type); + if (i < property.example.length - 1) { + object += `,`; + } + } + } + object += `]`; + return object; + } else if (property.type == "string") { + return `"${property.name}": ${checkExampleValues(property.example, property.type)}`; + } else { + return `"${property.name}": ${checkExampleValues(property.example, property.type)}`; + } + } + + /** + * Helper function to transform example values to valid json. + */ + function checkExampleValues(example, type) { + if (type == "integer" || type == "boolean" || type == "number") { + return example; + } else if (type == "object") { + let object = `"${example.name}": {`; + let i; + for (i = 0; i < _.values(example.properties).length; i++) { + object += checkProperty(_.values(example.properties)[i]); + if (i < _.values(property.properties).length - 1) { + object += `,`; + } + } + object += `}`; + return object; + } else if (type == "array") { + let object = `"${example.name}": [`; + let i; + for (i = 0; i < example.length; i++) { + object += checkProperty(_.values(example.properties)[i]); + if (i < _.values(example.properties).length - 1) { + object += `,`; + } + } + object += `]`; + return object; + } else if (type == "string") { + return `"${example}"`; + } else { + return `"${example}"`; + } + } +} diff --git a/templates/json_crud_server/README.md b/templates/json_crud_server/README.md new file mode 100644 index 0000000..d24b98a --- /dev/null +++ b/templates/json_crud_server/README.md @@ -0,0 +1,3 @@ +# {{openapi.info.title}} + +{{openapi.info.description}} diff --git a/templates/json_crud_server/config/common.yml.hbs b/templates/json_crud_server/config/common.yml.hbs new file mode 100644 index 0000000..89d34b8 --- /dev/null +++ b/templates/json_crud_server/config/common.yml.hbs @@ -0,0 +1,30 @@ +defaults: &defaults + api: + port: 3500 + + logger: + name: {{openapi.info.title}} + level: debug + levels: + trace: + debug: STDOUT + info: + warn: + error: STDERR + fatal: + +development: + <<: *defaults + +production: + <<: *defaults + + logger: + level: debug + levels: + trace: + debug: STDOUT + info: ./log/info.log + warn: ./log/warn.log + error: ./log/error.log + fatal: ./log/fatal.log diff --git a/templates/json_crud_server/log/.gitkeep b/templates/json_crud_server/log/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/json_crud_server/package.json b/templates/json_crud_server/package.json new file mode 100644 index 0000000..a630837 --- /dev/null +++ b/templates/json_crud_server/package.json @@ -0,0 +1,16 @@ +{ + "name": "{{package.name}}", + "description": "{{inline openapi.info.description}}", + "version": "{{openapi.info.version}}", + "scripts": { + "start": "node src/bin/www", + "dev": "node src/bin/www | bunyan" + }, + "dependencies": { + "body-parser": "1.13.2", + "bunyan": "1.5.1", + "cookie-parser": "1.3.5", + "express": "4.13.1", + "node-yaml-config": "0.0.3" + } +} diff --git a/templates/json_crud_server/src/api/constrains/$$schema$$.constrain.js.hbs b/templates/json_crud_server/src/api/constrains/$$schema$$.constrain.js.hbs new file mode 100644 index 0000000..6b8c829 --- /dev/null +++ b/templates/json_crud_server/src/api/constrains/$$schema$$.constrain.js.hbs @@ -0,0 +1,18 @@ +const errorModel = require('../helpers/errorResponse'); +function checkDublicateConstraints{{schema_name}}(entity, list){ + //if(){ + return true; + //}else{ + //throw new errorModel.ErrorResponse(15, `Variant with Name '${entity.name}' already exists.`, "test", 404, `Variant with Name '${entity.name}' already exists.`);} +} +function checkOtherConstraints{{schema_name}}(entity) +{ + //if(){ + return true; + //}else{ + //throw new errorModel.ErrorResponse(15, `Variant with Name '${entity.name}' already exists.`, "test", 404, `Variant with Name '${entity.name}' already exists.`);}} +} + module.exports = { + checkDublicateConstraints{{schema_name}}, + checkOtherConstraints{{schema_name}} +}; diff --git a/templates/json_crud_server/src/api/data/$$schema$$.data.json.hbs b/templates/json_crud_server/src/api/data/$$schema$$.data.json.hbs new file mode 100644 index 0000000..be57fa3 --- /dev/null +++ b/templates/json_crud_server/src/api/data/$$schema$$.data.json.hbs @@ -0,0 +1,5 @@ +[{ +{{#each schema_properties}} + {{{transformToJsonData this}}}{{#unless @last}},{{/unless}} + {{/each}} +}] diff --git a/templates/json_crud_server/src/api/helpers/errorResponse.js b/templates/json_crud_server/src/api/helpers/errorResponse.js new file mode 100644 index 0000000..25a5ad3 --- /dev/null +++ b/templates/json_crud_server/src/api/helpers/errorResponse.js @@ -0,0 +1,27 @@ +class ErrorResponse { + constructor( + errorCode, + title, + instance, + status, + detail + ) { + this.errorCode= errorCode; + this.title= title; + this.instance= instance; + this.status= status; + this.detail= detail; + } +} + +ErrorResponse.prototype.toString = function ErrorResponseToString() { + return '{' + + this.errorCode + " | " + + this.title + " | " + + this.instance + " | " + + this.status + " | " + + this.detail + '}' +} +module.exports = { + ErrorResponse +}; diff --git a/templates/json_crud_server/src/api/helpers/helper.js b/templates/json_crud_server/src/api/helpers/helper.js new file mode 100644 index 0000000..34b25d5 --- /dev/null +++ b/templates/json_crud_server/src/api/helpers/helper.js @@ -0,0 +1,100 @@ +const errorModel = require('../helpers/errorResponse'); +const fs = require('fs'); +const getNewId = (array) => { + if (array.length > 0) { + return array[array.length - 1].id + 1; + } else { + return 100000; + } +}; +const newDate = () => new Date().toString(); + + +function mustBeInArray(array, id) { + return new Promise((resolve, reject) => { + const row = array.find(r => r.id == id); + if (!row) { + reject(new errorModel.ErrorResponse(15,"Object with id "+ id + " was not found", "test", 404, "Object with id "+ id + " was not found")) + } + resolve(row); + }) +} + +function writeJSONFile(filename, content) { + fs.writeFileSync(filename, JSON.stringify(content), 'utf8', (err) => { + if (err) { + console.log(err); + } + }) +} + +function isArray(what) { + if (Object.prototype.toString.call(what) === '[object Array]'){ + throw new errorModel.ErrorResponse(15,"Now Array is allowed", "test", 400, "Now Array is allowed"); + } +} +function getUniqueElementsInArray(arr, comp) { + + const unique = arr + .map(e => e[comp]) + + // store the keys of the unique objects + .map((e, i, final) => final.indexOf(e) === i && i) + + // eliminate the dead keys & store unique objects + .filter(e => arr[e]).map(e => arr[e]); + + return unique; +} + +function checkLike(value, part){ + if(value.indexOf(part) > -1){ + return true; + }else{ + return false; + } +} +function filterValueWithOperator(filterParameter, operator, value, valueCollection, list){ + switch(operator){ + case "=": + if (!isArray(value)){ + return list.filter(x => x[filterParameter] == value); + } + case "!=": + if (!isArray(value)){ + return list.filter(x => x[filterParameter] != value); + } + case "IN": + if (Object.prototype.toString.call(valueCollection) === '[object Array]') { + filterlist = []; + valueCollection.forEach(x => filterlist.push(...list.filter(y => y[filterParameter] == x))); + return filterlist; + }else{ + return list.filter(x => x[filterParameter] == value); + } + return list.filter(x => x[filterParameter] == value); + case "NOT_IN": + if (Object.prototype.toString.call(valueCollection) === '[object Array]') { + filterlist = list.filter(x => !(valueCollection.includes( x[filterParameter]))); + return filterlist; + }else{ + return list.filter(x => x[filterParameter] != value); + } + case "LIKE": + return list.filter(x => checkLike(x[filterParameter],value)); + case "NOT_LIKE": + return list.filter(x => !(checkLike(x[filterParameter],value))); + default: + throw new errorModel.ErrorResponse(15,"Wrong Operator", "test", 400, "Wrong Operator"); + } +} +module.exports = { + getNewId, + newDate, + mustBeInArray, + writeJSONFile, + isArray, + getUniqueElementsInArray, + checkLike, + filterValueWithOperator +}; diff --git a/templates/json_crud_server/src/api/index.js.hbs b/templates/json_crud_server/src/api/index.js.hbs new file mode 100644 index 0000000..41bb17d --- /dev/null +++ b/templates/json_crud_server/src/api/index.js.hbs @@ -0,0 +1,46 @@ +const express = require('express'); +const cookieParser = require('cookie-parser'); +const bodyParser = require('body-parser'); +const config = require('../lib/config'); +const logger = require('../lib/logger'); + +const log = logger(config.logger); +const app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); + +/* + * Routes + */ +{{#each @root.openapi.endpoints}} +{{#endsWith @root.openapi.basePath '/'}} +app.use('{{@root.openapi.basePath}}{{..}}', require('./routes/{{..}}.route.js')); +{{else}} +app.use('{{@root.openapi.basePath}}/{{..}}', require('./routes/{{..}}.route.js')); +{{/endsWith}} +{{/each}} + +// catch 404 +app.use((req, res, next) => { + log.error(`Error 404 on ${req.url}.`); + res.status(404).send({ + errorCode: 15, + title: `URL ${req.url} not found.`, + instance: "test", + status: 404, + detail: `URL ${req.url} not found.` + }); +}); + +// catch errors +app.use((err, req, res, next) => { + const status = err.status || 500; + log.error(`Error ${status} (${err.toString()}) on ${req.method} ${req.url} with payload ${req.body}.`); + res.set('Content-Type', 'application/json'); + res.status(status).send(err); +}); + + + module.exports = app; diff --git a/templates/json_crud_server/src/api/models/$$schema$$.model.js.hbs b/templates/json_crud_server/src/api/models/$$schema$$.model.js.hbs new file mode 100644 index 0000000..d697755 --- /dev/null +++ b/templates/json_crud_server/src/api/models/$$schema$$.model.js.hbs @@ -0,0 +1,55 @@ +const {{camelCase this.schema_name}}Check = require('../constrains/{{camelCase this.schema_name}}.constrain.js'); +const errorModel = require('../helpers/errorResponse'); +const helper = require('../helpers/helper.js'); + +class {{schema_name}}Model { + constructor( + {{#each schema_properties}} + {{this.name}}{{#unless @last}},{{/unless}} + {{/each}} + ) { + {{#each schema_properties}} + this.{{this.name}}= {{this.name}}; + {{/each}} + } +} +function adaptModel(content, list, newInfo) { + let entity; + try{ + entity= new {{schema_name}}Model( + {{#each schema_properties}} + content.{{this.name}}{{#unless @last}},{{/unless}} + {{/each}} + ); + if(!newInfo){ + const index = list.findIndex(p => p.id == content.id); + {{#each schema_properties}} + if (entity.{{this.name}} == undefined){ + entity.{{this.name}} = list[index].{{this.name}}; + } + {{/each}} + } + if(checkRequiredConstraints{{schema_name}}(entity) && {{camelCase this.schema_name}}Check.checkDublicateConstraints{{schema_name}}(entity, list) && {{camelCase this.schema_name}}Check.checkOtherConstraints{{schema_name}}(entity)){ + return entity; + } + }catch (e) { + throw e; + } +} +function checkRequiredConstraints{{schema_name}}(entity){ + {{#each schema_properties_required}} + if(entity.{{this}} == undefined){ + throw new errorModel.ErrorResponse(15, `{{../schema_name}} required attribute '{{this}}'.`, "test", 400, `{{../schema_name}} required attribute '{{this}}'.`); + } + {{/each}} + return true; + } + {{schema_name}}Model.prototype.toString = function {{schema_name}}ModelToString() { + return '{' + {{#each schema_properties}} + this.{{this.name}}{{#unless @last}} + " | " + {{/unless}}{{/each}} + '}' + } +module.exports = { + {{schema_name}}Model, + adaptModel, + checkRequiredConstraints{{schema_name}} +}; diff --git a/templates/json_crud_server/src/api/routes/$$path$$.route.js.hbs b/templates/json_crud_server/src/api/routes/$$path$$.route.js.hbs new file mode 100644 index 0000000..2297408 --- /dev/null +++ b/templates/json_crud_server/src/api/routes/$$path$$.route.js.hbs @@ -0,0 +1,56 @@ +const express = require('express'); +const {{camelCase operation_name}} = require('../services/{{operation_name}}.service'); + +const router = new express.Router(); + +{{#each operation}} + {{#each this.path}} + {{#validMethod @key}} +/** + {{#each ../descriptionLines}} + * {{{this}}} + {{/each}} + */ +router.{{@key}}('{{../../subresource}}', async (req, res, next) => { + const options = { + {{#if ../requestBody}} + body: req.body{{#compare (lookup ../parameters 'length') 0 operator = '>' }},{{/compare}} + {{/if}} + {{#each ../parameters}} + {{#equal this.in "query"}} + {{../name}}: req.query.{{../name}}{{#unless @last}},{{/unless}} + {{/equal}} + {{#equal this.in "path"}} + {{../name}}: req.params.{{../name}}{{#unless @last}},{{/unless}} + {{/equal}} + {{#match @../key "(post|put)"}} + {{#equal ../in "body"}} + {{../name}}: req.body.{{../name}}{{#unless @last}},{{/unless}} + {{/equal}} + {{/match}} + {{/each}} + }; + + try { + const result = await {{camelCase ../../../operation_name}}.{{../operationId}}(options); + {{#ifNoSuccessResponses ../responses}} + res.status(200).send(result.data); + {{else}} + res.status(result.status || 200).send(result); + {{/ifNoSuccessResponses}} + } catch (err) { + {{#ifNoErrorResponses ../responses}} + return res.status(500).send({ + status: 500, + error: 'Server Error' + }); + {{else}} + next(err); + {{/ifNoErrorResponses}} + } +}); + + {{/validMethod}} + {{/each}} +{{/each}} +module.exports = router; diff --git a/templates/json_crud_server/src/api/services/$$path$$.service.js.hbs b/templates/json_crud_server/src/api/services/$$path$$.service.js.hbs new file mode 100644 index 0000000..361bd7f --- /dev/null +++ b/templates/json_crud_server/src/api/services/$$path$$.service.js.hbs @@ -0,0 +1,161 @@ +let {{camelCase operation_name}} = require('../data/{{camelCase operation_name}}.data.json'); +const {{camelCase operation_name}}Filename = './src/api/data/{{camelCase operation_name}}.data.json'; +{{#each operation}} +{{#NotUndefined this.path.additionalData}} +let {{camelCase ../path.additionalData}} = require('../data/{{camelCase ../path.additionalData}}.data.json'); +const {{camelCase../path.additionalData}}Filename = './src/api/data/{{camelCase ../path.additionalData}}.data.json'; +{{/NotUndefined}} +{{/each}} +const {{camelCase operation_name}}Model = require('../models/{{camelCase operation_name}}.model.js'); +{{#each operation}} +{{#NotUndefined this.path.additionalData}} +const {{camelCase../path.additionalData}}Model = require('../models/{{camelCase ../path.additionalData}}.model.js'); +{{/NotUndefined}} +{{/each}} +const errorModel = require('../helpers/errorResponse'); +const helper = require('../helpers/helper.js'); +{{#each operation}} + {{#each this.path}} + {{#validMethod @key}} +/** + * @param {Object} options +{{#each ../parameters}} +{{#if this.name}} + * @param {{../../../../openbrace}}{{capitalize type}}{{../../../../closebrace}} options.{{name}} {{inline description}} +{{/if}} +{{/each}} + * @throws {Error} + * @return {Promise} + */ +module.exports.{{../operationId}} = async (options) => { + {{#compare @key "get"}} + {{#endsWithLowerCase ../operationId ../idparameter}} + return new Promise((resolve, reject) => { + {{#each ../parameters}} + {{#equal this.in "path"}} + const {{{quote ../name}}}= options.{{../name}}; + {{/equal}} + {{/each}} + helper.mustBeInArray( {{camelCase ../../../operation_name}}, {{../idparameter}}) + .then(x => { + resolve(x) + }) + .catch(err => reject(err)) + }); + {{else}} + {{#NotUndefined ../additionalData}} + return new Promise((resolve, reject) => { + {{#each ../parameters}} + {{#equal this.in "path"}} + const {{{quote ../name}}}= options.{{../name}}; + {{/equal}} + {{/each}} + helper.mustBeInArray( {{camelCase ../../../operation_name}}, {{../idparameter}}) + .then(x => { + const {{camelCase ../additionalData}}Filtered = {{camelCase ../additionalData}}.filter(x => x.{{../idparameter}} == {{../idparameter}}); + if ({{camelCase ../additionalData}}Filtered.length === 0) { + resolve([]); + } + resolve({{camelCase ../additionalData}}Filtered); + }) + .catch(err => reject(err)) + }); + {{else}} + return new Promise((resolve, reject) => { + if ({{camelCase ../../../operation_name}}.length === 0) { + resolve([]); + } + resolve({{camelCase ../../../operation_name}}) + }); + {{/NotUndefined}} + {{/endsWithLowerCase}} + {{else}} + {{#compare @key "post"}} + {{#NotUndefined ../additionalData}} + return new Promise((resolve, reject) => { + {{#each ../parameters}} + {{#equal this.in "path"}} + const {{{quote ../name}}}= options.{{../name}}; + {{/equal}} + {{/each}} + helper.mustBeInArray( {{camelCase ../../../operation_name}}, {{../idparameter}}) + .then(x => { + let tempEntity = options.body; + helper.isArray(tempEntity); + const id = helper.getNewId({{camelCase ../additionalData}}); + tempEntity.id = id; + tempEntity.{{../idparameter}} = {{../idparameter}} + const {{camelCase ../additionalData}}New= {{camelCase ../additionalData}}Model.adaptModel(tempEntity,{{camelCase ../additionalData}}, true ); + {{camelCase ../additionalData}}.push({{camelCase ../additionalData}}New); + helper.writeJSONFile({{camelCase ../additionalData}}Filename, {{camelCase ../additionalData}}); + resolve({{camelCase ../additionalData}}New); + }) + .catch(err => reject(err)) + }); + + {{else}} + return new Promise((resolve, reject) => { + let tempEntity = options.body; + const id = helper.getNewId({{camelCase ../../../operation_name}}); + tempEntity.id = id; + const {{camelCase ../../../operation_name}}New = {{camelCase ../../../operation_name}}Model.adaptModel(tempEntity,{{camelCase ../../../operation_name}}, true ); + {{camelCase ../../../operation_name}}.push({{camelCase ../../../operation_name}}New); + helper.writeJSONFile({{camelCase ../../../operation_name}}Filename, {{camelCase ../../../operation_name}}); + resolve({{camelCase ../../../operation_name}}New) + }); + {{/NotUndefined}} + {{else}} + {{#compare @key "put"}} + {{#endsWithLowerCase ../operationId ../idparameter}} + return new Promise((resolve, reject) => { + let tempEntityUpdate = options.body; + let {{camelCase ../../../operation_name}}Updated = options.body; + {{#each ../parameters}} + {{#equal this.in "path"}} + const {{{quote ../name}}}= options.{{../name}}; + {{/equal}} + {{/each}} + helper.mustBeInArray( {{camelCase ../../../operation_name}}, {{../idparameter}}) + .then(x => { + const index = {{camelCase ../../../operation_name}}.findIndex(p => p.id == {{../idparameter}}); + tempEntityUpdate.id = x.id + const {{camelCase ../../../operation_name}}Updated = {{camelCase ../../../operation_name}}Model.adaptModel( tempEntityUpdate,{{camelCase ../../../operation_name}}, false ); + {{camelCase ../../../operation_name}}[index] = {...{{camelCase ../../../operation_name}}Updated}; + helper.writeJSONFile({{camelCase ../../../operation_name}}Filename, {{camelCase ../../../operation_name}}); + resolve({{camelCase ../../../operation_name}}[index]) + }) + .catch(err => reject(err)) + }); + {{else}} + //to implement + {{/endsWithLowerCase}} + {{else}} + {{#compare @key "delete"}} + {{#endsWithLowerCase ../operationId ../idparameter}} + return new Promise((resolve, reject) => { + {{#each ../parameters}} + {{#equal this.in "path"}} + const {{{quote ../name}}}= options.{{../name}}; + {{/equal}} + {{/each}} + helper.mustBeInArray( {{camelCase ../../../operation_name}}, {{../idparameter}}) + .then(x => { + {{camelCase ../../../operation_name}} = {{camelCase ../../../operation_name}}.filter(f => f.id != {{../idparameter}}) + helper.writeJSONFile({{camelCase ../../../operation_name}}Filename, {{camelCase ../../../operation_name}}); + resolve({status: 204}) + }) + .catch(err => reject(err)) + }); + {{else}} + //to implement + {{/endsWithLowerCase}} + {{else}} + //to implement + {{/compare}} {{/compare}} + {{/compare}} + {{/compare}} +}; + + {{/validMethod}} + {{/each}} +{{/each}} diff --git a/templates/json_crud_server/src/bin/www b/templates/json_crud_server/src/bin/www new file mode 100644 index 0000000..f3d6070 --- /dev/null +++ b/templates/json_crud_server/src/bin/www @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ +const app = require('../api'); +const http = require('http'); +const config = require('../lib/config'); +const logger = require('../lib/logger'); + +const log = logger(config.logger); + +/** + * Get port from environment and store in Express. + */ +const port = normalizePort(config.api.port || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ +const server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ +function normalizePort (val) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ +function onError (error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + log.fatal(`${bind} requires elevated privileges`); + process.exit(1); + break; + case 'EADDRINUSE': + log.fatal(`${bind} is already in use`); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ +function onListening () { + const addr = server.address(); + const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; + log.debug(`Listening on ${bind}`); +} diff --git a/templates/json_crud_server/src/lib/config.js b/templates/json_crud_server/src/lib/config.js new file mode 100644 index 0000000..1c0ec13 --- /dev/null +++ b/templates/json_crud_server/src/lib/config.js @@ -0,0 +1,6 @@ +const path = require('path'); +const yaml_config = require('node-yaml-config'); + +const config = yaml_config.load(path.join(__dirname, '../../config/common.yml')); + +module.exports = config; diff --git a/templates/json_crud_server/src/lib/logger.js b/templates/json_crud_server/src/lib/logger.js new file mode 100644 index 0000000..36ac60d --- /dev/null +++ b/templates/json_crud_server/src/lib/logger.js @@ -0,0 +1,32 @@ +const bunyan = require('bunyan'); + +/** + * @param {Object} config Logger configuration + */ +module.exports = config => { + const bunyanConfig = []; + const levels = Object.keys(config.levels); + + levels.forEach(level => { + const bunyanLevel = config.levels[level]; + if (!bunyanLevel) return; + + if (level === 'debug' && config.level !== 'debug') return; + + const logger = {level}; + + if (bunyanLevel === 'STDOUT') { + logger.stream = process.stdout; + } else if (bunyanLevel === 'STDERR') { + logger.stream = process.stderr; + } else if (bunyanLevel) { + logger.path = bunyanLevel; + } else { + return; + } + + bunyanConfig.push(logger); + }); + + return bunyan.createLogger({ name: config.name, streams: bunyanConfig }); +}; diff --git a/tests/openapi3/crud-json-server.yaml b/tests/openapi3/crud-json-server.yaml new file mode 100644 index 0000000..d4ad1d5 --- /dev/null +++ b/tests/openapi3/crud-json-server.yaml @@ -0,0 +1,1358 @@ +openapi: 3.0.0 +info: + title: CRUD Service + version: 1.0.0 + description: CRUD Service Example. + +servers: + - url: http://localhost:8080/crud-service/rest/v1 + description: Local development server. + +security: + - oidc: [] + +paths: + /configurations: + summary: Endpoint for handling the configuration. + description: Endpoint for execution operations on CRUD configuration. + + get: + tags: + - "configuration" + summary: Lists all configuration. + description: Returns all configuration available in the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Configuration" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "configuration" + summary: Add a configuration. + description: Add a new configuration to the database. + requestBody: + description: The configuration to be created. + content: + application/json: + schema: + $ref: "#/components/schemas/Configuration" + responses: + "200": + description: "configuration was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/Configuration" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /configurations/{configurationId}: + summary: Endpoint for handling configurations specified by their IDs. + description: Endpoint for handling configurations specified by their IDs. + parameters: + - name: configurationId + in: path + description: "ID of configuration" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "configuration" + summary: Read a configuration. + description: Read a single configuration by its ID. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Configuration" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + delete: + tags: + - "configuration" + summary: Delete a configuration. + description: Removes a configuration from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "configuration" + summary: Update a configuration. + description: Updates an existing configuration in the database. + requestBody: + description: The configuration's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/Configuration" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Configuration" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /states: + summary: Endpoint for handling the State. + description: Endpoint for execution operations on CRUD State. + + get: + tags: + - "state" + summary: Lists all States. + description: Returns all State available in the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/State" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "state" + summary: Add State. + description: Add a new State to the database. + requestBody: + description: The State to be created. + content: + application/json: + schema: + $ref: "#/components/schemas/State" + responses: + "200": + description: "State was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/State" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /states/{stateId}: + summary: Endpoint for handling States specified by their IDs. + description: Endpoint for handling States specified by their IDs. + parameters: + - name: stateId + in: path + description: "ID of State" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "state" + summary: Read a State. + description: Read a single State by its ID. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/State" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + delete: + tags: + - "state" + summary: Delete a State. + description: Removes a State from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "state" + summary: Update a State. + description: Updates an existing State in the database. + requestBody: + description: The State's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/State" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/State" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + /phases/{phaseId}/comments: + summary: Endpoint for handling comments. + description: Endpoint for handling comments belonging to a specific phase. + parameters: + - name: phaseId + in: path + description: "ID of the parent phase" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "comment" + summary: Lists all comments belonging to a specific phase by phaseId. + description: Returns all the comments for the specified phase from the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Comment" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "comment" + summary: Add a comment belonging to a specific phase by phaseId. + description: Add a new comment belonging to a specific phase to the database. + requestBody: + description: The comment to be created. + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + responses: + "200": + description: "comment was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + /demands/{demandId}/comments: + summary: Endpoint for handling comments. + description: Endpoint for handling comments belonging to a specific phase. + parameters: + - name: demandId + in: path + description: "ID of the parent phase" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "comment" + summary: Lists all comments belonging to a demand by demandId. + description: Returns all the comments for the demand from the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Comment" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "comment" + summary: Add a comment belonging to a specific demand by demandId. + description: Add a new comment belonging to a specific demand to the database. + requestBody: + description: The comment to be created. + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + responses: + "200": + description: "comment was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + /comments/{commentId}: + summary: Endpoint for handling comment specified by their IDs. + description: Endpoint for handling comment specified by their IDs. + parameters: + - name: commentId + in: path + description: "ID of comment" + required: true + schema: + type: "integer" + format: "int64" + + delete: + tags: + - "comment" + summary: Delete a comment. + description: Removes a comment from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "comment" + summary: Update a comment. + description: Updates an existing comment in the database. + requestBody: + description: The comment's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Comment" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /variants: + summary: Endpoint for handling the variants. + description: Endpoint for execution operations on CRUD variants. + + get: + tags: + - "variant" + summary: Lists all variants. + description: Returns all the variants available in the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Variant" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "variant" + summary: Add variant. + description: Add a new variant to the database. + requestBody: + description: The variant to be created. + content: + application/json: + schema: + $ref: "#/components/schemas/Variant" + responses: + "200": + description: "variant was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/Variant" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /variants/{variantId}: + summary: Endpoint for handling variants specified by their IDs. + description: Endpoint for handling variants specified by their IDs. + parameters: + - name: variantId + in: path + description: "ID of variant to return" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "variant" + summary: Read a variant. + description: Read a single variant by its ID. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Variant" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + delete: + tags: + - "variant" + summary: Delete variant. + description: Removes a variant from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "variant" + summary: Update variant. + description: Updates an existing variant in the database. + requestBody: + description: The variant's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/Variant" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Variant" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /variants/{variantId}/phases: + summary: Endpoint for handling phases. + description: Endpoint for handling phases belonging to a specific variant. + parameters: + - name: variantId + in: path + description: "ID of the parent variant" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "phase" + summary: Lists all phases. + description: Returns all the phases for the specified variant from the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Phase" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "phase" + summary: Create new phase. + description: Adds a new phase to the variant specified by its ID. + requestBody: + description: The new phase's properties. + content: + application/json: + schema: + $ref: "#/components/schemas/Phase" + responses: + "200": + description: " phase was created successfully" + content: + application/json: + schema: + $ref: "#/components/schemas/Phase" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + /phases/{phaseId}: + summary: Endpoint for handling phases. + description: Endpoint for handling phases specified by their ID. + parameters: + - name: phaseId + in: path + description: "ID of the phase" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "phase" + summary: Reads a phase. + description: Reads one phase from the database specified by its ID. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Phase" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + delete: + tags: + - "phase" + summary: Delete phase. + description: Removes a phase from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "phase" + summary: Update phase. + description: Updates an existing phase in the database. + requestBody: + description: The phase's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/Phase" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Phase" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + /phases/{phaseId}/demands: + summary: Endpoint for handling demands. + description: Endpoint for handling demands belonging to a specific phase. + parameters: + - name: phaseId + in: path + description: "ID of the parent phase" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "demand" + summary: Lists all demands belonging to a specific phase by phaseId. + description: Returns all the demands for the specified phase from the database. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/Demand" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + post: + tags: + - "demand" + summary: Add a demand belonging to a specific phase by phaseId. + description: Adds a new demand to the phase specified by its ID. + requestBody: + description: The new demand's properties. + content: + application/json: + schema: + $ref: "#/components/schemas/Demand" + responses: + "200": + description: "succesful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Demand" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + /demands/{demandId}: + summary: Endpoint for handling demands. + description: Endpoint for handling demands specified by their ID. + parameters: + - name: demandId + in: path + description: "ID of the demand" + required: true + schema: + type: "integer" + format: "int64" + + get: + tags: + - "demand" + summary: Reads a demand. + description: Reads one demand from the database specified by its ID. + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Demand" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + delete: + tags: + - "demand" + summary: Delete demand. + description: Removes a demand from the database. + responses: + "204": + description: "no content" + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + put: + tags: + - "demand" + summary: Update demand. + description: Updates an existing demand in the database. + requestBody: + description: The demand's properties to be updated. + content: + application/json: + schema: + $ref: "#/components/schemas/Demand" + responses: + "200": + description: "successful operation" + content: + application/json: + schema: + $ref: "#/components/schemas/Demand" + "400": + $ref: '#/components/responses/BadRequestError' + "401": + $ref: '#/components/responses/UnauthorizedError' + "403": + $ref: '#/components/responses/ForbiddenError' + "404": + $ref: '#/components/responses/NotFoundError' + "405": + $ref: '#/components/responses/MethodNotAllowedError' + "409": + $ref: '#/components/responses/ConflictError' + "412": + $ref: '#/components/responses/PreconditionFailedError' + "500": + $ref: '#/components/responses/InternalServerError' + default: + $ref: '#/components/responses/ApplicationError' + + + +components: + + securitySchemes: + oidc: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + + Configuration: + description: The configuration as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The configuration' s internal ID + example: 123456 + externalId: + type: "string" + description: The external id as string. + example: "C0001" + name: + type: "string" + description: The configuration as String. + example: "Configuration 1" + required: + - name + + State: + description: The State as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The State's internal ID. + example: 123456 + name: + type: "string" + description: The State's name. + example: "failed" + required: + - name + + Variant: + description: The variant as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The variant's internal ID. + example: 123456 + name: + type: "string" + description: The variant's name. + example: "Variant" + creationInfo: + $ref: "#/components/schemas/CreationInfo" + modificationInfo: + $ref: "#/components/schemas/ModificationInfo" + objectVersionInfo: + $ref: "#/components/schemas/ObjectVersionInfo" + required: + - name + + Phase: + description: The phase as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The phase's internal ID. + example: 123456 + variantId: + type: "integer" + format: "int64" + description: The ID of the phase's parent variant. + example: 123456 + availableConfigurationIds: + type: array + items: + type: "integer" + format: "int64" + description: The configurations available in this phase. + example: [123456, 789123] + name: + type: "string" + description: The phase's name. + example: "A 1.3" + pattern: '^[A-Z]( )?[0-9]\.[0-9]$' + creationInfo: + $ref: "#/components/schemas/CreationInfo" + modificationInfo: + $ref: "#/components/schemas/ModificationInfo" + objectVersionInfo: + $ref: "#/components/schemas/ObjectVersionInfo" + required: + - name + - variantId + - availableConfigurationIds + + Demand: + description: The demand as it is used in the CRUD service. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The demand's internal ID. + example: 123456 + phaseId: + type: "integer" + format: "int64" + description: The parent phase's internal ID. + example: 123456 + configuration: + $ref: "#/components/schemas/Configuration" + state: + $ref: "#/components/schemas/State" + usage: + type: "string" + description: The demand's usage. + example: "Sample Usage" + creationInfo: + $ref: "#/components/schemas/CreationInfo" + modificationInfo: + $ref: "#/components/schemas/ModificationInfo" + objectVersionInfo: + $ref: "#/components/schemas/ObjectVersionInfo" + required: + - phaseId + - configuration + - usage + + Comment: + description: A comment created by a user. + type: object + properties: + id: + type: "integer" + format: "int64" + description: The comment's internal ID. + example: 123456 + comment: + type: "string" + description: The comment as it was enetered by the user. + example: "Some comment" + phaseId: + type: "integer" + format: "int64" + example: 123456 + demandId: + type: "integer" + format: "int64" + example: 123456 + creationInfo: + $ref: "#/components/schemas/CreationInfo" + required: + - comment + + ModificationInfo: + description: The modification info contains meta information about the data object's last modification. + type: object + properties: + updatedBy: + type: "string" + description: The unique ID of the user, who committed the latest modifications. + example: "suessmma" + updatedAt: + type: "string" + format: "date-time" + description: The date and time when the data object was updated. + example: "2020-02-23T18:25:43.511Z" + required: + - updatedBy + - updatedAt + + ObjectVersionInfo: + description: The version info describes the current version of the objects in this application. + type: object + properties: + objectVersion: + type: "integer" + format: "int32" + description: The object's internal version. + example: 1 + required: + - objectVersion + + CreationInfo: + description: The creation info contains meta information about the data object's creation. + type: object + properties: + createdBy: + type: "string" + description: The unique ID of the user, who created the data object. + example: "suessmma" + createdAt: + type: "string" + format: "date-time" + description: The date and time when the data object was created. + example: "2020-02-23T18:25:43.511Z" + required: + - createdBy + - createdAt + + + ApplicationErrorResponse: + description: Scheme describing an internal application error, which will be shown to the user. + type: object + properties: + errorCode: + type: "integer" + format: "int64" + description: A unique error code defined in the service to specify the cause of the error. + example: 15 + title: + type: "string" + description: A concise description of the error. + example: "Unknown ID" + instance: + type: "string" + description: A unique error ID. + example: "bbcec01a-da1c-4437-8f77-17ede1bb2d2a" + status: + type: "integer" + description: The HTTP status code. + example: 500 + detail: + type: "string" + description: Optional attribute to show the stacktrace in the response. + example: "com.bmw.heat.exception.SomeException: SomeException error occurred!\r\n\tat..." + required: + - errorCode + - title + - instance + - status + + + responses: + BadRequestError: + description: A bad request error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + UnauthorizedError: + description: A unauthorized error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + ForbiddenError: + description: A forbidden error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + NotFoundError: + description: A not found error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + MethodNotAllowedError: + description: A method not allowed error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + ConflictError: + description: Another request has updated or deleted the same object. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + PreconditionFailedError: + description: A precondition was violated + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + InternalServerError: + description: A internal server error occured. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" + + ApplicationError: + description: An other error occurred. The response content describes the error's details. + content: + application/json: + schema: + $ref: "#/components/schemas/ApplicationErrorResponse" \ No newline at end of file