My API seems to no longer parse properly using swagger-parser and swagger-tools latest versions after we did a full npm update on all our deps last night. However, I cannot track down what is causing these failures to happen since the API follows proper formatting for the 2.0 spec according to the docs.
Some information on the current setup:
swagger-parser: v3.3.0
swagger-tools: v0.9.7
node: v4.2.1
npm: v2.14.7
We are making use of swagger-tools connect middleware to create a stand-alone API app. Our setup for the api looks like this:
var app = require('connect')();
var cors = require('cors');
var swaggerTools = require('swagger-tools');
var swaggerParser = require('swagger-parser');
app.use(cors());
app.initialize = function (done) {
swaggerParser.parse('./api/swagger.yaml', function (err, api) {
if (err) throw err;
swaggerTools.initializeMiddleware(api, function (middleware) {
app.use(middleware.swaggerMetadata());
app.use(middleware.swaggerValidator());
var options = {
controllers: './controllers',
useStubs: process.env.NODE_ENV === 'development' ? true : false
};
app.use(middleware.swaggerRouter(options));
app.use(middleware.swaggerUi());
typeof done === 'function' && done();
});
});
return app;
};
Prior to the updates on the deps everything worked fine like this. However, now when being initialized our API is throwing a bunch of errors when swaggerTools.initializeMiddleware is being called.
A few chunks of our API are as follows:
./api/swagger.yaml
swagger: '2.0'
info:
version: 0.0.1
title: Insert API Title Here
schemes:
- http
basePath: /api/v1
consumes:
- application/json
produces:
- application/json
paths:
/users:
$ref: './api/paths/users.yaml'
/users/{userId}:
$ref: './api/paths/users-userId.yaml'
definitions:
User:
$ref: './api/models/user.yaml'
parameters:
userId:
in: path
name: userId
description: The user id of the user object.
required: true
type: integer
offset:
name: offset
in: query
description: The record to start the return set at.
required: false
type: integer
default: 0
minimum: 0
limit:
name: limit
in: query
description: The quota to limit the return set to.
required: false
type: integer
default: 10
orderBy:
name: orderBy
in: query
description: The field to order by.
required: false
type: string
sort:
name: sort
in: query
description: The sort order of the return set.
required: false
type: string
enum: [desc, asc]
default: asc
./api/paths/users.yaml
x-swagger-router-controller: Users
get:
tags:
- users
summary: Gets a list of all users.
description: ''
operationId: getUsers
parameters:
- $ref: '#/parameters/offset'
- $ref: '#/parameters/limit'
- $ref: '#/parameters/orderBy'
- $ref: '#/parameters/sort'
responses:
200:
description: OK
schema:
$ref: '#/definitions/UserCollection'
401:
description: Not Authorized
The errors we are seeing are things like this now:
#/paths/~1users/get/parameters/1/name: Parameter already defined: undefined
#/paths/~1users/get/parameters/2/name: Parameter already defined: undefined
#/paths/~1users/get/parameters/3/name: Parameter already defined: undefined
#/paths/~1users~1{userId}/get: API requires path parameter but it is not defined: userId
#/paths/~1users~1{userId}/put/parameters/1/name: Parameter already defined: undefined
#/paths/~1users~1{userId}/put: API requires path parameter but it is not defined: userId
#/paths/~1users~1{userId}/delete: API requires path parameter but it is not defined: userId
#/paths/~1users~1{userId}~1profile/get: API requires path parameter but it is not defined: userId
#/paths/~1users~1{userId}~1profile/post/parameters/1/name: Parameter already defined: undefined
#/paths/~1users~1{userId}~1profile/post: API requires path parameter but it is not defined: userId
I'm not sure where to go from here since I have tried everything to keep the API layout the same (broken into multiple files) vs. having to resort to putting it all into a single file. Our API is rather large and it is much easier to maintain broken into parts like this which used to work fine without issue.
Is there something I am missing, a step that needs to be done differently with the updates to swagger-parser and swagger-tools? Any help is appreciated.
It seems the jump between v2 to v3 of Swagger-Parser has changed the functionality of .parse() to no longer resolve references. Because of this it was causing portions of the API to not validate properly. Switching to .validate() instead of .parse() has fixed this issue. A handful of adjustments had to be made to the .yaml files to make it work with the new 2.0 standards but all is working again.
Related
My Angular client application is using MSAL in order to communicate with Azure B2C and retrieve the id token, refresh token and access token. These are in my localstorage after login.
I'm also using openapi generator to generate my services for my client application. The thing is, whenever I make a call to my backend, it is resulting as a 401.
The controller:
[Authorize]
[RequiredScope("example.read")]
[HttpGet(Name = "GetJobs")]
public async Task<ActionResult<IEnumerable<JobDTO>>> Index(int pageNumber, int pageSize, string? titleDesc, string? category)
{
var owner = CheckClaimMatch(ClaimTypes.NameIdentifier);
try
{
var result = await _jobModel.GetAllJobs(pageNumber, pageSize, titleDesc, category);
return Ok(result);
} catch (Exception ex)
{
return BadRequest(ex);
}
}
And here is my (simplified) openapi configuration:
openapi: 3.0.3
info:
title: EXAMPLE REST API
description: Api for the new example application
version: 1.0.0
servers:
- url: "https://{hostname}:{port}/{basePath}/"
variables:
hostname:
default: localhost
port:
default: "7051"
basePath:
default: api
tags:
- name: Job
paths:
/Jobs:
get:
operationId: getJobs
security:
- AzureOAuth:
- example.read
tags:
- Job
parameters:
- in: query
name: titleDesc
schema:
type: string
responses:
200:
description: Returns requested page
content:
application/json:
schema:
$ref: "#/components/schemas/JobPage"
components:
securitySchemes:
AzureOAuth:
type: oauth2
description: This API uses OAuth2 with authorizationCode grant flow.
flows:
authorizationCode:
authorizationUrl: https://example.b2clogin.com/example.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/authorize
tokenUrl: https://example.b2clogin.com/example.onmicrosoft.com/B2C_1_SignUpAndSignIn/oauth2/v2.0/token
scopes:
example.read: read
example.write: write
schemas:
Job:
type: object
required:
- title
- category
- teleworkingDays
description: Job information
properties:
id:
type: integer
format: int64
title:
type: string
security:
- AzureOAuth:
- example.read
- example.write
I'm using oauth2 according to Azure's documentation.
Here is the post executed by the generated service.
Notice that the Bearer is empty.
I'm also wondering, if it's the correct way to do, because here I'm implementing it with the oauth2 way, but as I already have an access token in my localstorage, shouldn't just go with the bearer implementation ?
Also, testing with postman works completely fine, so I assume the controller part is fine.
Thank you
Following code is written in a loop in a express route handler;
const projects = await Projects.find({});//working fine
for (let i = 0; i < projects.length; i++) {
const project = projects[i];
const build = await Build.find({ id: project.latest_build});
//logger.debug(build[0].status); //error
const features = await Feature.find({ build_id: project.latest_build});
logger.debug(features[0].status);
}
The above code gives error at liine 4.
(node:672375) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined
However, It is correctly printed in the logs. Seems like the value of the variable is being filled by a promise lazily.
But Line number 6 always works fine.
Note: I'm not performing any other read/write operations on above collections.
Update
id is unique key for Build collection.
build_id is normal field for Feature collection.
Schemas and Documents are created like this
Schemas.Projects = new mongoose.Schema({
id: {
type: String,
unique: true
},
title: String,
latest_build: String
});
Schemas.Build = new mongoose.Schema({
id: {
type: String,
unique: true
},
run_date: Date,
status: String,
});
Schemas.Feature = new mongoose.Schema({
id : String,
build_id: String,
summary : String,
status: String,
flows: Number,
});
const Projects = mongoose.model('Projects', Schemas.Projects);
const Build = mongoose.model(`Build_${projId}`, Schemas.Build);
const Feature = mongoose.model(`Feature_${projId}`, Schemas.Feature);
The problem with the code in question is that it was overly optimistic in its expectations. This line...
const build = await Build.find({ id: project.latest_build });
logger.debug(build[0].status)
... assumed the query always finds at least one Build document. But, as the code was running as a part of the loop, the results were actually mixed:
at the first iteration, the query did find the Build object by the first project's data, and correctly logged its status
at the second iteration it gave back an empty array instead. Then the code threw an Error on attempting to access status property of build[0] (which was undefined).
The appropriate solution for this issue depends on how you should treat those cases. If each Project must have a corresponding Build (it's a strictly 1-1 relation), then having an Error thrown at you is actually fine, as you just have to fix it.
Still, it might be worth treating it as a 'known error', like this:
const buildId = project.latest_build;
const builds = await Build.find({ id: buildId });
if (!builds.length) {
logger.error(`No build found for ${buildId}`);
continue;
}
In this case (assuming logger is set up correctly) you won't have your code blowing up, yet the error will be logged. But if it's actually an ok situation to have no builds yet, just drop the logging - and treat this case as a known edge case.
I created a server stub using my own openapi-3.0.0 template from openapi-generator.
While implementing the API logic for creating resource, I'm referencing a component declared under requestBodies as follows
components:
requestBodies:
SamyojyaUser:
content:
application/json:
schema:
$ref: '#/components/schemas/SamyojyaUser'
application/xml:
schema:
$ref: '#/components/schemas/SamyojyaUser'
description: Samyojya User object that needs to be added to the system
required: true
The API is declared as follows
paths:
/samyojya-user:
post:
operationId: addSamyojyaUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/requestBodies/SamyojyaUser'
x-content-type: application/json
However, while processing the request Ajv complains saying
can't resolve reference #/components/requestBodies/SamyojyaUser from id #
Looking like there seems to be some issue with registering requestBodies component. I see the other components showing up in the Ajv when I debug. I am tempted to use the user component directly at the path object but I want to customize the request body further. Any thoughts on tweaking it further?
References to #/components/requestBodies/... can only be used directly under requestBody and not under schema:
paths:
/samyojya-user:
post:
operationId: addSamyojyaUser
requestBody:
$ref: '#/components/requestBodies/SamyojyaUser'
Also, the indentation in the request body component in wrong - the description and required: true must be on the same level as content.
components:
requestBodies:
SamyojyaUser:
content:
application/json:
schema:
$ref: '#/components/schemas/SamyojyaUser'
application/xml:
schema:
$ref: '#/components/schemas/SamyojyaUser'
# vvvv Note the indentation level
description: Samyojya User object that needs to be added to the system
required: true
Use https://editor.swagger.io (or other validators) to validate your OpenAPI definitions before generating code.
I am working on express js with swagger.
I have created some end points with defining respective controller and function call.
tabs/users:
# binds a127 app logic to a route
x-swagger-router-controller: tabs
get:
description: Returns list of all users
# used as the method name of the controller
operationId: getUsers
parameters:
- name: name
in: query
description: name
required: false
type: string
responses:
"200":
description: Success
schema:
# a pointer to a definition
$ref: "#/definitions/TabResponse"
# responses may fall through to errors
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
Like oprtionId getUsers, I have multiple functions.
Inside controller tabs.js I have functions like
function getUser(){
//business logic
}
Now I have one use case for every call to my end points I need to validate the token which user sent with each request is valid or not.
For this, I need to add validation function call to each controller function.
But I am looking for the central solution so that every call should go through validation function.
Please suggest your thoughts on it.
I'm running an Express app with Mongoose communicating with a Mongo DB. I have a simple page CMS to update values. On my staging environment when I update the page I can see the update reflected in my Mongo console, so I know the change is persisting to the DB. However, when I make a call to my API I see a cached response. If I restart Node I'll see the correct updated value.
Oddly enough I have other Mongoose models which seem to update fine. This also does not happen in my local development environment. Next steps to debug this would be very helpful as I can't track down where the issue would lie. I can only assume my staging environment Mongo DB has some sort of caching for this single collection and not the rest, is this a possibility?
Here's my model schema:
import mongoose, { Schema } from 'mongoose';
const HomePage = new Schema({
marquee: {
image: String,
label: String,
headline: String,
copy: String,
linkUrl: String,
linkText: String,
videoText: String,
videoUrl: String
},
updatedAt: {
type: Date,
default: Date.now
},
createdAt: {
type: Date
}
}, {
collection: 'homepage'
});
export default mongoose.model('HomePage', HomePage);
Additionally, in my API's response header, I have the cache set to: Cache-Control:max-age=0
I narrowed this down to the browser caching the response, which is odd as I've built other applications on this same platform without the issue. To resolve the issue I simply added a ? to my URL path. So my final get request is now as follows: http://myapp.com/api/homepage?