Embedded object in loopback model schema for Swagger - swagger

In the context of a remote method, I'm trying to define a model schema of a parameter passed in the body. This object looks like this:
{
name: "Alex",
credentials: {
user: "alex",
pass: "pass"
}
}
So, I have this code in my remote method definition:
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {
"name": "string",
"credentials": {
"type": "object",
"properties": {
"user": "string",
"pass: "string"
}
}
}, http: {source: 'body'}, required: true
}
],
.....
Unfortunatelly, the details of this embedded object (credentials) are not shown in the generated Swagger explorer. This is what I see:
{
"user": "string",
"credentials": {}
}
I've tried many different ways but I could not show the properties of the credentials object.
Any ideas?

Loopback 2.x
Edit: Note the following only works for Loopback 2.x, as the type registry changed in 3.x.
The problem is that the data you are providing needs to be on the type property for the nested value. This should work:
MyModel.remoteMethod('postSomething', {
accepts: [
{
arg: 'person',
type: {
name: 'string',
credentials: {
type: {
user: 'string',
pass: 'string'
}
}
},
http: {
source: 'body'
},
required: true
}
],
//...
This also works with arrays:
accepts: [
{
arg: 'Book',
type: {
title: 'string',
author: 'string',
pages: [{
type: {
pageNo: 'number',
text: 'string'
}
}]
}
}
],
// ...
Loopback 3.x
Since the model registry and strong remoting changed in Loopback 3.x to only allow string or array types, you can't really avoid creating a new model. If you would like to quickly 'inline' a model without going through the full process of adding the model json file, adding it to model-config.json etc. you can register it directly on the app:
app.registry.createModel('Person', {
firstName: 'string',
lastName: 'string'
}, { base: 'Model' });
You can set the base to one of your other models if you want to extend an existing model (e.g, add another property that is only accepted in the given remote method)
If you want to create the model without cluttering up your model registry, you can do so by calling createModel on loobpack itself:
const loopback = require('loopback')
const modl = loopback.createModel({
name: 'Person',
base: null,
properties: {
firstName: {
type: 'string',
id: true // means it won't have an id property
}
}
});
In both of the above examples, you refer to the model by name to attach it to the remote method:
accepts: [
{
arg: 'Person',
type: 'Person'
}
],
// ...
Note you will need to create a sub-model for every sub-property (e.g. credentials)

Loopback swagger only picks up the outer object ignoring the properties of the object.
If you want to show a nested object in the swagger docs for the request body, you will have to make nested model.
Assuming you have a model called as person. You have to create another model named "credentials" having properties user and password. Then define the relationship in your person model's config
{
"name": "Person",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"credentials": {
"type": "embedsOne",
"model": "credentials",
"property": "credentials",
"options": {
"validate": true,
"forceId": false
}
}
},
"acls": [],
"methods": {}
}
And add a reference to this model where you define your remote method
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {Person},
http: {source: 'body'}, required: true
}
],
To avoid "Treating unknown remoting type" warning make sure your model is marked as "public" inside your "model-config.json"

Related

Is parent definition in API request body required if it's partial sub child properties are marked required?

Suppose I have the following OpenAPI definition (ignore the indentation or missing attributes):
FooData:
type: object
additionalProperties: false
description: Holds the foo data Information.
required:
- id
- contact
properties:
id:
description: Identifier
type: string
maxLength: 255
minLength: 1
pattern: ^[a-zA-Z0-9\-]{1,255}$
identity:
description: Holds customer identity info.
$ref: '#/MyIdentity' ....
MyIdentity:
type: object
additionalProperties: false
description: Holds the customer identity information.
required:
- userId
properties:
userId:
type: integer
format: int64
minimum: 0
maximum: 9223372036854775807
description: Unique Identification Number which is specific to individual user.
firstName:
type: string
description: user's First name.
maxLength: 32
minLength: 1
pattern: ^[a-zA-Z\s]{1,32}$ ....
As you see, MyIdentity is not required in FooData however its property userID is marked as required.
Can we ignore the full MyIdentity object in the request body while hitting the API?
Assuming the request body schema is FooData:
Can we ignore the full MyIdentity object in the request body while hitting the API?
Yes - because the FooData.identity property is not required so it can be omitted. However, if the FooData.identity property is present, it must be an object and contain at least the userId property.
In other words, the following FooData payloads are valid:
{
"id": "abc",
"contact": "..."
}
{
"id": "abc",
"contact": "...",
"identity": {
"userId": 12345
}
}
but these payloads are invalid:
// No `userId` in `identity`
{
"id": "abc",
...,
"identity": {}
}
// No `userId` in `identity`
{
"id": "abc",
...,
"identity": {
"firstName": "Alex"
}
}
// `identity` is not an object
{
"id": "abc",
...,
"identity": null
}
// `identity` is not an object
{
"id": "abc",
...,
"identity": "blah"
}

NestJs Swagger body array with mixed types

I have created an endpoint to create bouchers but they can be created with any feature and each feature has a diferent type.
Example:
POST /code
{
"code": "<String>"
"features": [{
"type": "expiredDate",
"expiredDate": "<ISODate>"
}, {
"type": "referrer",
"refererId": "<UUID>"
}]
}
But it could also be used like this:
POST /code
{
"code": "<String>"
"features": [{
"type": "referrer",
"refererId": "<UUID>"
}]
}
or
POST /code
{
"code": "<String>"
"features": [{
"type": "motivated-purchase",
"pursache": "<UUID>"
}]
}
or... many similars things you know
How can I especify it in nestjs for swagger? I tried with anyOf but I can't get it
If someone knows how to do it with the openapi nomenclature, it could also help me
I answer myself...
First you should create the diferents features DTO
export default class ExpiredDateFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['expiredDate'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly expiredDate: string;
}
export default class ReferrerFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['referrer'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly refererId: string;
}
export default class ExpiredDateFeatureDto {
#ApiProperty({ type: String, required: true, enum: ['motivated-purchase'] })
readonly type: string;
#ApiProperty({ type: String, required: true })
readonly pursache: string;
}
And add it in the main dto
#ApiExtraModels(ExpiredDateFeatureDto, ReferrerFeatureDto, ExpiredDateFeatureDto)
export default class CouponDto {
#ApiProperty()
readonly code: string;
#ApiProperty({
type: 'array',
items: {
oneOf: [{
$ref: getSchemaPath(ExpiredDateFeatureDto)
}, {
$ref: getSchemaPath(ReferrerFeatureDto)
}, {
$ref: getSchemaPath(ExpiredDateFeatureDto)
}],
},
})
#IsOptional()
readonly features: FeatureDto[];
}
It is very important to add the annotation #ApiExtraModels in the main dto

Loopback POST array of entry?

I want to insert 10 entries with one query against 10 queries.
I read that it's possible to do it by sending an array like this :
But I get this error:
Do I need to set something? I don't know what to do at all.
Repo with a sample : https://github.com/mathias22osterhagen22/loopback-array-post-sample
Edit:
people-model.ts:
import {Entity, model, property} from '#loopback/repository';
#model()
export class People extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<People>) {
super(data);
}
}
export interface PeopleRelations {
// describe navigational properties here
}
export type PeopleWithRelations = People & PeopleRelations;
The problem with your code was :
"name": "ValidationError", "message": "The People instance is not
valid. Details: 0 is not defined in the model (value: undefined);
1 is not defined in the model (value: undefined); name can't be
blank (value: undefined).",
Here in above as in your #requestBody schema, you are applying to insert a single object property, where as in your body are sending the array of [people] object.
As you can see in your people.model.ts you have declared property name to be required, so system finds for the property "name", which obviously not available in the given array of object as primary node.
As you are passing index array, so its obvious error that you don't have any property named 0 or 1, so it throws error.
The below is the code hat you should apply to get insert the multiple, items of the type.
#post('/peoples', {
responses: {
'200': {
description: 'People model instance',
content: {
'application/json': {
schema: getModelSchemaRef(People)
}
},
},
},
})
async create(
#requestBody({
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(People, {
title: 'NewPeople',
exclude: ['id'],
}),
}
},
},
})
people: [Omit<People, 'id'>]
): Promise<{}> {
people.forEach(item => this.peopleRepository.create(item))
return people;
}
You can also use this below
Promise<People[]> {
return await this.peopleRepository.createAll(people)
}
You can pass the array of your people model by modifying the request body.If you need more help you can leave comment.
I think you have a clear solution now. "Happy Loopbacking :)"

How to configure a non-embedded hasMany relationship with breeze.js

I'm trying to configure a few models with breeze.js
The first is session and I was able to get this working perfectly with a simple query like the below
var query = breeze.EntityQuery.from("sessions").toType("Session");
..but when I tried to add a related "speakers" array to the session I seem to be left with an empty array after the materialization step is complete
Do I need to write a custom json adapter for an api that looks like this?
/api/sessions/
[
{
"id": 1,
"name": "javascript",
"speakers": [
1
]
}
]
/api/speakers/1/
{
"id": 1,
"name": "Toran",
"session": 1
}
Here is my current model configuration (mostly working)
var ds = new breeze.DataService({
serviceName: 'api',
hasServerMetadata: false,
useJsonp: false
});
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
this.instance = new breeze.EntityManager({dataService: ds});
this.instance.metadataStore.addEntityType({
shortName: "Speaker",
namespace: "App",
dataProperties: {
id: { dataType: "Int64", isPartOfKey: true },
name: { dataType: "String" },
session: { dataType: "Int64" }
},
navigationProperties: {
session: {
entityTypeName: "Session", isScalar: true,
associationName: "session", foreignKeyNames: ["session"]
}
}
});
this.instance.metadataStore.addEntityType({
shortName: "Session",
namespace: "App",
dataProperties: {
id: { dataType: "Int64", isPartOfKey: true },
name: { dataType: "String" },
speakers: { dataType: "Undefined" }
},
navigationProperties: {
speakers: {
entityTypeName: "Speaker", isScalar: false,
associationName: "speakers", foreignKeyNames: ["speakers"]
}
}
});
Thanks for the help !
note** I'm using ember.js (not backbone/ko/angular)
I tried to keep the above brief but if you need absolutely everything, checkout this single js file
https://github.com/toranb/embereeze/blob/master/website/static/website/js/app.js
update
if I remove the "speakers" dataProperty breeze gets fairly unhappy in this block of code (as it's not a dataProperty on the session model)
var fkProps = fkNames.map(function (fkName) {
//fkName is speakers .... yet it's not a data property so bad things happen :(
return parentEntityType.getDataProperty(fkName);
});
var fkPropCollection = parentEntityType.foreignKeyProperties;
// Array.prototype.push.apply(parentEntityType.foreignKeyProperties, fkProps);
fkProps.forEach(function (dp) {
I just did a PR - the association name should be the same on both ends, much like any relational database would have.
this.instance.metadataStore.addEntityType({
shortName: "Speaker",
namespace: "App",
dataProperties: {
id: { dataType: "Int64", isPartOfKey: true },
name: { dataType: "String" },
session: { dataType: "Int64" }
},
navigationProperties: {
sessionModel: {
entityTypeName: "Session", isScalar: true,
associationName: "Speaker_Sessions", foreignKeyNames: ["session"]
}
}
});
this.instance.metadataStore.addEntityType({
shortName: "Session",
namespace: "App",
dataProperties: {
id: { dataType: "Int64", isPartOfKey: true },
name: { dataType: "String" }
},
navigationProperties: {
speakers: {
entityTypeName: "Speaker", isScalar: false,
associationName: "Speaker_Sessions"
}
}
});
So I renamed the association, removed the extra speakers property on the Session (there is no property for the relationship, simply an inverse that should be mapped) and then removed the foreign key on the session as you don't need one there. By assigning passing session down from the API with the models shown above should be enough to let Breeze know
Speakers' have a session property, which is a foreign key pointing to a single session.
Sessions have a collection of speakers, which should be mapped on the other end

Update node key index in batch operation

Short
I can't get neo to update existing node properties in a batched rest op.
Long
I want to create a batch operation that inserts/updates a node inside an index. It should handle three use-cases:
if the node does not exist, insert it with the given properties set
if the node exists, update it's properties set with the new values, if any.
I'm using the batch operation api, I wrote a test where I'm issuing two requests:
Short
1. first one inserts the node and indexes it's properties
2. second one simply updates some properties of the node
Here's the first request:
[
{
"method": "POST",
"to": "/index/node/events?uniqueness=get_or_create",
"id": 1,
"body": {
"key": "id",
"value": "222222222",
"properties": {
"id": "222222222",
"type": "event-type"
}
}
},
{
"method": "POST",
"to": "/index/node/events",
"body": {
"uri": "{1}",
"key": "id",
"value": "222222222"
}
},
{
"method": "POST",
"to": "/index/node/events",
"body": {
"uri": "{1}",
"key": "type",
"value": "event-type"
}
} ]
And now the second one.
[
{
method: 'POST',
to: '/index/node/events?uniqueness=get_or_create',
id: 1,
body: {
key: 'id',
value: '222222222',
properties: {id: '222222222', type: 'event-type', title: 'SUPEREVENT'}
}
},
{
method: 'POST',
to: '/index/node/events',
body: {
uri: '{1}',
key: 'id',
value: '222222222'
}
},
{
method: 'POST',
to: '/index/node/events',
body: {
uri: '{1}',
key: 'type',
value: 'event-type'
}
},
{
method: 'POST',
to: '/index/node/events',
body: {
uri: '{
1
}',
key: 'title',
value: 'SUPEREVENT'
}
}
]
NOTE! that on the second request i'm adding and event title property with value SUPEREVENT. This does not get persisted nor indexed. Why? and how can I fix it?
Thank you,
Alex
From the doc:
URL Parameter uniqueness=get_or_create: Create a new node/relationship
and index it if no existing one can be found. If an existing
node/relationship is found, discard the sent data and return the
existing node/relationship.
So your second request's data will be discarded. You need to split each of your requests in two requests each, and do two batches. Each of the two batches has two instructions: first one creates the node if not present, and the second one updates the properties.

Resources