google cloud endpoints body array - google-app-engine

We have a rest API that is written in Java (hosted in Wildfly). Our service is running in kubernetes (GKE). We want to leverage Cloud Endpoints to track usage and responsiveness of our API. The API is not new, we have been shipping software that interacts with it for years. It is also quite large (thousands of public methods). We have Swagger documentation for our API, and have no validation errors. When I try to deploy our Swagger using:
gcloud beta service-management deploy swagger.yaml
It is not successful. I get the following error repeated 237 times:
ERROR: unknown location: http: body field path 'body' must be a non-repeated message.
I have tracked it down to 237 methods that include a json array in a body parameter. In our API these are methods that either accept or return a list of objects.
Is there any way I can get this accepted by service-management deploy? Changing our API isn't an option, but we would really like to be able to use endpoints.
For example, this method signature:
#PUT
#Path ("/foobars/undelete")
#Consumes (MediaType.APPLICATION_JSON)
#Produces (MediaType.APPLICATION_JSON)
#ApiOperation (value = "Undelete foobars")
#ApiResponses (value =
{
#ApiResponse (
code = 200,
message = "foobars undeleted",
response = FooBar.class,
responseContainer = "List"
) , #ApiResponse (
code = 206,
message = "Not all foobars undeleted",
response = FooBar.class,
responseContainer = "List"
) , #ApiResponse (
code = 410,
message = "Not found"
) , #ApiResponse (
code = 500,
message = "Server Error"
)
})
public Response undeleteFooBars (#ApiParam (value = "FooBar ID List") List<UUID> entityIds)
generates this swagger snippet:
"/foobars/undelete":
put:
tags:
- foo
summary: Undelete FooBars
description: ''
operationId: undeleteFooBars
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: body
description: FooBar ID List
required: false
schema:
type: array
items:
type: string
format: uuid
responses:
'200':
description: Foo Bars undeleted
schema:
type: array
items:
"$ref": "#/definitions/FooBar"
'206':
description: Not all FooBars undeleted
schema:
type: array
items:
"$ref": "#/definitions/FooBar"
'410':
description: Not found
'500':
description: Server Error

I have had the exact same problem with Endpoints, where it does not seem to think that passing an array of objects is valid as a body parameter. I worked around this by just using a generic object and a decent description. The description will not programatically fix anything, but using a generic object allows Endpoints to work and the description gives information to the consumer of the API for what is expected.
parameters:
- in: body
name: body
description: Array of FooBar objects
required: false
schema:
type: object
This seems like an oversight on the part of the Endpoints team IMHO as using an array of objects in the body fits fine within the OpenApi spec and works with tools like http://editor.swagger.io/
Edit: I should also add that it is generally bad practice to use just a raw array as a request body or response body as it can cause a contract breaking change if additional properties are desired in the future, like say a count or pagination information.
If this is an existing API and you are just documenting the existing contract, then this solution will work to get the job done, but if you are designing a new API, then a better definition would be:
parameters:
- in: body
name: body
description: All the FooBar objects
required: false
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/definitions/FooBarResource'
Since this could later be extended to add additional properties like
parameters:
- in: body
name: body
description: All the FooBar objects
required: false
schema:
type: object
properties:
count:
type: integer
description: The total count of resources
callbackUrl:
type: string
description: The URL to trigger once creation is complete
items:
type: array
items:
$ref: '#/definitions/FooBarResource'
description: The resources to create

You can do better than a plain object. Yyou can specify an array as the the value of an object with a single key. This way you preserve your type information:
parameters:
- description: "Your items to add"
in: body
name: listings
required: true
schema:
type: object
properties:
payload:
type: array
maxItems: 1000
minItems: 1
$ref: "#/definitions/listing"
It's ugly but at least it documents whatever the model you are passing in should look like.

Related

Creating ruleset for API Governance

I'm trying creaiting ruleset for RAML to check if there are the example for responses and description for uriParams.
/example:
/{uriParams}:
get:
uriParameters:
uriParams:
description: Example description uriParams
body:
application/json:
example: !include examples.example.json
And for this I create two ruleset but it's not working:
response-example:
message: Provide example.
targetClass: apiContract.Example
and:
- propertyConstraints:
apiContract.returns:
atLeast:
count: 1
validation:
propertyConstraints:
apiContract.structuredValue:
pattern: "^!include"
uri-descriptions:
message: Provide descriptions.
targetClass: apiContract.Parameter
if:
propertyConstraints:
apiContract.Parameter:
pattern: uri
then:
propertyConstraints:
core.description:
minCount: 1
Checking examples is non-trivial. AMF (RAML parser used for Governance) resolves them to the schema of the parameter, payload, header, etc. Just clarifying with an example: imagine that if you have a response of type Person and an example there, the example will be transferred from the response to the Person type.
Fortunately AMF keeps an annotation called tracked-element that points back to the original parameter, payload, header, etc. where the example was defined. Unfortunately checking that these are the same element must be done with custom Rego code.
Here's the resulting ruleset:
profile: My Ruleset
description: Example ruleset
violation:
- provide-examples-on-payloads
- provide-description-on-parameters
validations:
provide-examples-on-payloads:
message: Always include examples in request and response bodies
targetClass: apiContract.Payload
rego: |
schema = find with data.link as $node["http://a.ml/vocabularies/shapes#schema"]
nested_nodes[examples] with data.nodes as object.get(schema, "http://a.ml/vocabularies/apiContract#examples", [])
examples_from_this_payload = { element |
example = examples[_]
sourcemap = find with data.link as object.get(example, "http://a.ml/vocabularies/document-source-maps#sources", [])
tracked_element = find with data.link as object.get(sourcemap, "http://a.ml/vocabularies/document-source-maps#tracked-element", [])
tracked_element["http://a.ml/vocabularies/document-source-maps#value"] = $node["#id"]
element := example
}
$result := (count(examples_from_this_payload) > 0)
provide-description-on-parameters:
message: Always include examples in URI parameters
targetClass: apiContract.Parameter
if:
propertyConstraints:
apiContract.binding:
in: ['path']
then:
propertyConstraints:
core.description:
minCount: 1

discord.js slash command api loading error

I am trying to create a /config <subcommand> system for my bot, but it gives me an error with the slash command body.
Here is the current body I have for the command
name: 'config',
description: "The config system for the bot",
userPermissions: ['MANAGE_GUILD'],
options: [
{
name: 'prefix',
description: "Set a different prefifor the server",
type: "SUB_COMMAND",
options: [
{name: "new-prefix", description: "The new prefix that you want to set", type: "STRING", required: true}
]
}
]
The error is
DiscordAPIError[50035]: Invalid Form Body
0.options[0].options[0].type[NUMBER_TYPE_COERCE]: Value "STRING" is not int.
0.options[0].type[NUMBER_TYPE_COERCE]: Value "SUB_COMMAND" is not int.
I have never encountered this error before
As you are using raw json to register your commands, the type of your options must be an integer, instead of a string.
Why? Because Discord API accepts integers, not strings.
Each type has an associated id integer as documented here, which is what you have to use instead of strings.
In your case, type: "SUB_COMMAND" should be type: 1
Why options types are documented as strings in the discord.js docs? Because they made it strings to improve code readability. The lib does "convert" these strings to int before requesting the API. But you are using raw json in this case.
You can check for other application commands options type in Discord Developer Documentation

How to display complex objects in multipart/form-data requests in Swagger UI?

I am using OpenAPI 3.0.1 and I am trying to send a request as multipart/form-data. The request body is defined as follows:
requestBody:
content:
multipart/form-data: # Media type
schema: # Request payload
type: object
properties: # Request parts
media: # Part 1 (string value)
type: string
address: # Part2 (object)
type: object
properties:
street:
type: string
city:
type: string
profileImage: # Part 3 (an image)
type: string
format: binary
Here, the address field is an object. Swagger UI shows the address field itself, but does not show its properties street, city. Why is that?
This issue was fixed in Swagger UI v. 3.51.0. Make sure you're using the latest version.

Meteor inserting into a collection schema with array elements

Hi I created a SimpleSchema for a Mongo collection which has a variable number of sub-documents called measurables. Unfortunately it's been a while since I've done this and I can't remember how to insert into this type of schema! Can someone help me out?
The schema is as follows:
const ExerciseTemplates = new Mongo.Collection('ExerciseTemplates');
const ExerciseTemplateSchema = new SimpleSchema({
name: {
type: String,
label: 'name',
},
description: {
type: String,
label: 'description',
},
createdAt: {
type: Date,
label: 'date',
},
measurables: {
type: Array,
minCount: 1,
},
'measurables.$': Object,
'measurables.$.name': String,
'measurables.$.unit': String,
});
ExerciseTemplates.attachSchema(ExerciseTemplateSchema);
The method is:
Meteor.methods({
addNewExerciseTemplate(name, description, measurables) {
ExerciseTemplates.insert({
name,
description,
createdAt: new Date(),
measurables,
});
},
});
The data sent by my form for measurables is an array of objects.
The SimpleSchema docs seem to be out of date. If I use the example they show with measurables: type: [Object] for an array of objects. I get an error that the the type can't be an array and I should set it to Array.
Any suggestions would be awesome!!
Many thanks in advance!
edit:
The measurable variable contains the following data:
[{name: weight, unit: kg}]
With the schema above I get no error at all, it is silent as if it was successful, but when I check the db via CLI I have no collections. Am I doing something really stupid? When I create a new meteor app, it creates a Mongo db for me I assume - I'm not forgetting to actually create a db or something dumb?
Turns out I was stupid. The schema I posted was correct and works exactly as intended. The problem was that I defined my schema and method in a file in my imports directory, outside both client and server directories. This methods file was imported into the file with the form that calls the method, and therefore available on the client, but not imported into the server.
I guess that the method was being called on the client as a stub so I saw the console.log firing, but the method was not being called on the server therefore not hitting the db.
Good lesson for me regarding the new recommended file structure. Always import server side code in server/main.js!!! :D
Thanks for your help, thought I was going to go mad!

node.js: JSON dot notation + access array key

I have a site setup using node.js, express, mongoDB & mongoose. Mongoose is giving me an array that contains this:
[ { title: 'HTML in depth',
author: 'kevin vanhove',
_id: 53039f264a6324978c70084b,
chapters:
[ { _id: 53039f264a6324978c70084d,
content: '/jow',
title: 'History of HTML' },
{ _id: 53039f264a6324978c70084c,
content: '/w3c',
title: 'What is the W3C' } ],
__v: 0 } ]
I'm getting this result in my terminal using this code:
function indexData(books){
console.log(books)
res.render("index.html", {context:books, partials:{footer:"footer", header:"header"}})
}
Now i'm trying to access the value in chapters[0].title, but i'm getting an error:
console.log(books[0].chapters[0].title)
TypeError: Cannot read property '0' of undefined
I'm confused here, am i accessing the value using the right dot notation?
UPDATE 1: complete terminal output
24 Feb 10:56:47 - [nodemon] restarting due to changes...
24 Feb 10:56:47 - [nodemon] starting `node app.js`
Mongoose: books.find({}) { fields: undefined }
/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/routes/routes.js:45
console.log(books[0].chapters[0].title)
^
TypeError: Cannot read property '0' of undefined
at indexData (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/routes/routes.js:45:32)
at Promise.<anonymous> (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/routes/routes.js:37:31)
at Promise.<anonymous> (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/node_modules/mpromise/lib/promise.js:177:8)
at Promise.EventEmitter.emit (events.js:95:17)
at Promise.emit (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/node_modules/mpromise/lib/promise.js:84:38)
at Promise.fulfill (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/node_modules/mpromise/lib/promise.js:97:20)
at /Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/lib/query.js:1052:26
at model.Document.init (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/lib/document.js:250:11)
at completeMany (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/lib/query.js:1050:12)
at Object.cb (/Users/kevinvanhove/Documents/work/projects/basing/business/klanten/javascript/nodeJS/express/basing/node_modules/mongoose/lib/query.js:1017:11)
24 Feb 10:56:49 - [nodemon] app crashed - waiting for file changes before starting...
Partial solution:
I was trying to get to the chapters data. Updating my mongoose schema to include the chapters data did the trick.
var booksSchema = new mongoose.Schema({
title: String,
author: String,
chapters: [
{
title: String,
content: String
}
]
});
This doesn't work an that way on the Mongoose (server) side. The result from find() is a cursor that you need to either iterate or convert via toArray from the native driver part. Something like this
(Assuming Schema Books):
Books.collection.find({}).toArray(function(err, results) {
// results is an array that you can do something with here.
});
Of course if you have defined additional Schema for arrays and their documents that are not embedded, then this is going to hurt a bit more and you need to use the iterative methods to get your results as an array.
If you want specific items from your books and sub-items within, then you would be better of tuning your find query using the operators rather than returning all results. Or even the aggregation pipeline may be of some help.

Resources