@kravc/schema 2.7.6 → 2.8.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -14
- package/dist/CredentialFactory.d.ts +345 -0
- package/dist/CredentialFactory.d.ts.map +1 -0
- package/dist/CredentialFactory.js +381 -0
- package/dist/CredentialFactory.js.map +1 -0
- package/dist/Schema.d.ts +448 -0
- package/dist/Schema.d.ts.map +1 -0
- package/dist/Schema.js +506 -0
- package/dist/Schema.js.map +1 -0
- package/dist/ValidationError.d.ts +70 -0
- package/dist/ValidationError.d.ts.map +1 -0
- package/dist/ValidationError.js +78 -0
- package/dist/ValidationError.js.map +1 -0
- package/dist/Validator.d.ts +483 -0
- package/dist/Validator.d.ts.map +1 -0
- package/dist/Validator.js +570 -0
- package/dist/Validator.js.map +1 -0
- package/dist/helpers/JsonSchema.d.ts +99 -0
- package/dist/helpers/JsonSchema.d.ts.map +1 -0
- package/dist/helpers/JsonSchema.js +3 -0
- package/dist/helpers/JsonSchema.js.map +1 -0
- package/dist/helpers/cleanupAttributes.d.ts +34 -0
- package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
- package/dist/helpers/cleanupAttributes.js +113 -0
- package/dist/helpers/cleanupAttributes.js.map +1 -0
- package/dist/helpers/cleanupNulls.d.ts +27 -0
- package/dist/helpers/cleanupNulls.d.ts.map +1 -0
- package/dist/helpers/cleanupNulls.js +96 -0
- package/dist/helpers/cleanupNulls.js.map +1 -0
- package/dist/helpers/createSchemasMap.d.ts +67 -0
- package/dist/helpers/createSchemasMap.d.ts.map +1 -0
- package/dist/helpers/createSchemasMap.js +200 -0
- package/dist/helpers/createSchemasMap.js.map +1 -0
- package/dist/helpers/getReferenceIds.d.ts +169 -0
- package/dist/helpers/getReferenceIds.d.ts.map +1 -0
- package/dist/helpers/getReferenceIds.js +241 -0
- package/dist/helpers/getReferenceIds.js.map +1 -0
- package/dist/helpers/got.d.ts +60 -0
- package/dist/helpers/got.d.ts.map +1 -0
- package/dist/helpers/got.js +72 -0
- package/dist/helpers/got.js.map +1 -0
- package/dist/helpers/mapObjectProperties.d.ts +150 -0
- package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
- package/dist/helpers/mapObjectProperties.js +229 -0
- package/dist/helpers/mapObjectProperties.js.map +1 -0
- package/dist/helpers/normalizeAttributes.d.ts +213 -0
- package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
- package/dist/helpers/normalizeAttributes.js +243 -0
- package/dist/helpers/normalizeAttributes.js.map +1 -0
- package/dist/helpers/normalizeProperties.d.ts +168 -0
- package/dist/helpers/normalizeProperties.d.ts.map +1 -0
- package/dist/helpers/normalizeProperties.js +223 -0
- package/dist/helpers/normalizeProperties.js.map +1 -0
- package/dist/helpers/normalizeRequired.d.ts +159 -0
- package/dist/helpers/normalizeRequired.d.ts.map +1 -0
- package/dist/helpers/normalizeRequired.js +206 -0
- package/dist/helpers/normalizeRequired.js.map +1 -0
- package/dist/helpers/normalizeType.d.ts +81 -0
- package/dist/helpers/normalizeType.d.ts.map +1 -0
- package/dist/helpers/normalizeType.js +210 -0
- package/dist/helpers/normalizeType.js.map +1 -0
- package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
- package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
- package/dist/helpers/nullifyEmptyValues.js +191 -0
- package/dist/helpers/nullifyEmptyValues.js.map +1 -0
- package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
- package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
- package/dist/helpers/removeRequiredAndDefault.js +138 -0
- package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
- package/dist/helpers/validateId.d.ts +39 -0
- package/dist/helpers/validateId.d.ts.map +1 -0
- package/dist/helpers/validateId.js +51 -0
- package/dist/helpers/validateId.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/ld/documentLoader.d.ts +8 -0
- package/dist/ld/documentLoader.d.ts.map +1 -0
- package/dist/ld/documentLoader.js +24 -0
- package/dist/ld/documentLoader.js.map +1 -0
- package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
- package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
- package/dist/ld/getLinkedDataAttributeType.js +32 -0
- package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
- package/dist/ld/getLinkedDataContext.d.ts +19 -0
- package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
- package/dist/ld/getLinkedDataContext.js +50 -0
- package/dist/ld/getLinkedDataContext.js.map +1 -0
- package/eslint.config.mjs +32 -52
- package/examples/credentials/createAccountCredential.ts +27 -0
- package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
- package/examples/index.ts +7 -0
- package/examples/schemas/FavoriteItemSchema.ts +27 -0
- package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
- package/examples/schemas/PreferencesSchema.ts +29 -0
- package/examples/schemas/ProfileSchema.ts +91 -0
- package/examples/schemas/Status.yaml +3 -0
- package/examples/schemas/StatusSchema.ts +12 -0
- package/jest.config.mjs +5 -0
- package/package.json +27 -20
- package/src/CredentialFactory.ts +392 -0
- package/src/Schema.ts +583 -0
- package/src/ValidationError.ts +90 -0
- package/src/Validator.ts +603 -0
- package/src/__tests__/CredentialFactory.test.ts +588 -0
- package/src/__tests__/Schema.test.ts +371 -0
- package/src/__tests__/ValidationError.test.ts +235 -0
- package/src/__tests__/Validator.test.ts +787 -0
- package/src/helpers/JsonSchema.ts +119 -0
- package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
- package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
- package/src/helpers/__tests__/createSchemasMap.test.ts +238 -0
- package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
- package/src/helpers/__tests__/got.test.ts +193 -0
- package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
- package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
- package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
- package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
- package/src/helpers/__tests__/normalizeType.test.ts +772 -0
- package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
- package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
- package/src/helpers/__tests__/validateId.test.ts +118 -0
- package/src/helpers/cleanupAttributes.ts +151 -0
- package/src/helpers/cleanupNulls.ts +106 -0
- package/src/helpers/createSchemasMap.ts +212 -0
- package/src/helpers/getReferenceIds.ts +273 -0
- package/src/helpers/got.ts +73 -0
- package/src/helpers/mapObjectProperties.ts +272 -0
- package/src/helpers/normalizeAttributes.ts +247 -0
- package/src/helpers/normalizeProperties.ts +249 -0
- package/src/helpers/normalizeRequired.ts +233 -0
- package/src/helpers/normalizeType.ts +235 -0
- package/src/helpers/nullifyEmptyValues.ts +207 -0
- package/src/helpers/removeRequiredAndDefault.ts +151 -0
- package/src/helpers/validateId.ts +53 -0
- package/src/index.ts +17 -0
- package/src/ld/__tests__/documentLoader.test.ts +57 -0
- package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
- package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
- package/src/ld/documentLoader.ts +28 -0
- package/src/ld/getLinkedDataAttributeType.ts +46 -0
- package/src/ld/getLinkedDataContext.ts +80 -0
- package/tsconfig.json +27 -0
- package/types/credentials-context.d.ts +14 -0
- package/types/security-context.d.ts +6 -0
- package/examples/Status.yaml +0 -3
- package/examples/createAccountCredential.js +0 -27
- package/examples/createMineSweeperScoreCredential.js +0 -63
- package/examples/index.js +0 -9
- package/src/CredentialFactory.js +0 -67
- package/src/CredentialFactory.spec.js +0 -131
- package/src/Schema.js +0 -104
- package/src/Schema.spec.js +0 -172
- package/src/ValidationError.js +0 -31
- package/src/Validator.js +0 -128
- package/src/Validator.spec.js +0 -355
- package/src/helpers/cleanupAttributes.js +0 -71
- package/src/helpers/cleanupNulls.js +0 -42
- package/src/helpers/getReferenceIds.js +0 -71
- package/src/helpers/mapObject.js +0 -65
- package/src/helpers/normalizeAttributes.js +0 -28
- package/src/helpers/normalizeProperties.js +0 -61
- package/src/helpers/normalizeRequired.js +0 -37
- package/src/helpers/normalizeType.js +0 -41
- package/src/helpers/nullifyEmptyValues.js +0 -57
- package/src/helpers/removeRequiredAndDefault.js +0 -30
- package/src/helpers/validateId.js +0 -19
- package/src/index.d.ts +0 -25
- package/src/index.js +0 -8
- package/src/ld/documentLoader.js +0 -25
- package/src/ld/documentLoader.spec.js +0 -12
- package/src/ld/getLinkedDataContext.js +0 -63
- package/src/ld/getLinkedDataType.js +0 -38
- /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
- /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
package/src/CredentialFactory.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Schema = require('./Schema')
|
|
4
|
-
const Validator = require('./Validator')
|
|
5
|
-
const validateId = require('./helpers/validateId')
|
|
6
|
-
const { constants } = require('credentials-context')
|
|
7
|
-
|
|
8
|
-
const { CREDENTIALS_CONTEXT_V1_URL } = constants
|
|
9
|
-
|
|
10
|
-
class CredentialFactory {
|
|
11
|
-
// TODO: Add exception when added two schemas with the same name.
|
|
12
|
-
constructor(uri, schemas) {
|
|
13
|
-
validateId('uri', uri)
|
|
14
|
-
|
|
15
|
-
this._types = [].concat(schemas).map(schema => {
|
|
16
|
-
if (schema.url) {
|
|
17
|
-
return schema
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return new Schema(schema, schema.id, uri)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
this._uri = uri
|
|
24
|
-
this._context = {}
|
|
25
|
-
this._validator = new Validator(this._types)
|
|
26
|
-
|
|
27
|
-
for (const { id, linkedDataType } of this._types) {
|
|
28
|
-
this._context[id] = linkedDataType
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get credentialType() {
|
|
33
|
-
const [ credentialType ] = this._uri.split('/').reverse()
|
|
34
|
-
|
|
35
|
-
return credentialType
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get context() {
|
|
39
|
-
return {
|
|
40
|
-
[this.credentialType]: { '@id': this._uri },
|
|
41
|
-
...this._context
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
createCredential(id, holder, subject = {}) {
|
|
46
|
-
validateId('id', id)
|
|
47
|
-
validateId('holder', holder)
|
|
48
|
-
|
|
49
|
-
const type = [ 'VerifiableCredential', this.credentialType ]
|
|
50
|
-
|
|
51
|
-
const [ rootType ] = this._types
|
|
52
|
-
const credentialSubject = this._validator.validate(subject, rootType.id)
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
'@context': [
|
|
56
|
-
CREDENTIALS_CONTEXT_V1_URL,
|
|
57
|
-
this.context
|
|
58
|
-
],
|
|
59
|
-
id,
|
|
60
|
-
type,
|
|
61
|
-
holder,
|
|
62
|
-
credentialSubject
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
module.exports = CredentialFactory
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Schema = require('./Schema')
|
|
4
|
-
const { expect } = require('chai')
|
|
5
|
-
const { canonize } = require('jsonld')
|
|
6
|
-
const documentLoader = require('./ld/documentLoader')
|
|
7
|
-
const CredentialFactory = require('./CredentialFactory')
|
|
8
|
-
const {
|
|
9
|
-
createAccountCredential,
|
|
10
|
-
createMineSweeperScoreCredential
|
|
11
|
-
} = require('examples')
|
|
12
|
-
|
|
13
|
-
describe('CredentialFactory', () => {
|
|
14
|
-
describe('CredentialFactory.constructor(uri, schemas)', () => {
|
|
15
|
-
it('throws error if "uri" parameter is missing', () => {
|
|
16
|
-
expect(
|
|
17
|
-
() => new CredentialFactory()
|
|
18
|
-
).to.throw()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('throws error if "uri" parameter is not a URL', () => {
|
|
22
|
-
expect(
|
|
23
|
-
() => new CredentialFactory('BAD_URL')
|
|
24
|
-
).to.throw()
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('.createCredential(id, holder, subject = {}, subjectTypeId = null)', () => {
|
|
29
|
-
let factory
|
|
30
|
-
|
|
31
|
-
before(() => {
|
|
32
|
-
const videoGameSchema = new Schema({
|
|
33
|
-
id: {},
|
|
34
|
-
name: { type: 'string', required: true },
|
|
35
|
-
version: { type: 'string', required: true }
|
|
36
|
-
}, 'VideoGame', 'https://schema.org/')
|
|
37
|
-
|
|
38
|
-
factory = new CredentialFactory('https://example.com/StarCraft', videoGameSchema)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('returns single schema based credential', async () => {
|
|
42
|
-
const credential = await createAccountCredential('did:PLAYER_ID', 'CAHTEP')
|
|
43
|
-
|
|
44
|
-
expect(credential).to.exist
|
|
45
|
-
await canonize(credential, { documentLoader })
|
|
46
|
-
|
|
47
|
-
const { credentialSubject: { createdAt } } = credential
|
|
48
|
-
expect(credential).to.eql({
|
|
49
|
-
'@context': [
|
|
50
|
-
'https://www.w3.org/2018/credentials/v1',
|
|
51
|
-
{
|
|
52
|
-
AccountV1: { '@id': 'https://example.com/schema/AccountV1' },
|
|
53
|
-
Account: {
|
|
54
|
-
'@id': 'https://example.com/schema/AccountV1#Account',
|
|
55
|
-
'@context': {
|
|
56
|
-
'@vocab': 'https://example.com/schema/AccountV1#',
|
|
57
|
-
'@version': 1.1,
|
|
58
|
-
'@protected': true,
|
|
59
|
-
schema: 'https://schema.org/',
|
|
60
|
-
username: { '@id': 'username' },
|
|
61
|
-
createdAt: { '@id': 'createdAt', '@type': 'schema:DateTime' },
|
|
62
|
-
dateOfBirth: { '@id': 'dateOfBirth', '@type': 'schema:Date' }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
id: 'https://example.com/account/CAHTEP',
|
|
68
|
-
type: [ 'VerifiableCredential', 'AccountV1' ],
|
|
69
|
-
holder: 'did:PLAYER_ID',
|
|
70
|
-
credentialSubject: {
|
|
71
|
-
id: 'did:PLAYER_ID',
|
|
72
|
-
username: 'CAHTEP',
|
|
73
|
-
createdAt,
|
|
74
|
-
type: 'Account'
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('returns multiple schemas based credential', async () => {
|
|
80
|
-
const playerScore = {
|
|
81
|
-
wins: 5,
|
|
82
|
-
losses: 5,
|
|
83
|
-
winRate: 50,
|
|
84
|
-
UNDEFINED: 'VALUE',
|
|
85
|
-
bestScore: 23450,
|
|
86
|
-
endurance: 'P5M22S',
|
|
87
|
-
dateCreated: '2020-10-10T00:00:00Z',
|
|
88
|
-
bestRoundTime: 10000
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const credential = await createMineSweeperScoreCredential(
|
|
92
|
-
'did:GAME_ID',
|
|
93
|
-
'did:PLAYER_ID',
|
|
94
|
-
playerScore
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
expect(credential).to.exist
|
|
98
|
-
await canonize(credential, { documentLoader })
|
|
99
|
-
|
|
100
|
-
const { credentialSubject } = credential
|
|
101
|
-
expect(credentialSubject.type).to.equal('Player')
|
|
102
|
-
expect(credentialSubject.hasVideoGameScore.type).to.equal('VideoGameScore')
|
|
103
|
-
expect(credentialSubject.hasVideoGameScore.game.type).to.equal('VideoGame')
|
|
104
|
-
expect(credentialSubject.hasVideoGameScore.UNDEFINED).to.not.exist
|
|
105
|
-
|
|
106
|
-
const customContext = credential['@context'][1]
|
|
107
|
-
expect(customContext.VideoGame).to.eql({
|
|
108
|
-
'@id': 'https://schema.org/VideoGame',
|
|
109
|
-
'@context': {
|
|
110
|
-
'@protected': true,
|
|
111
|
-
'@version': 1.1,
|
|
112
|
-
'@vocab': 'https://schema.org/',
|
|
113
|
-
name: { '@id': 'name' },
|
|
114
|
-
version: { '@id': 'version' }
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('throws error if "id" parameter is missing', () => {
|
|
120
|
-
expect(
|
|
121
|
-
() => factory.createCredential()
|
|
122
|
-
).to.throw('Parameter "id" is required')
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('throws error if "holder" parameter is missing', () => {
|
|
126
|
-
expect(
|
|
127
|
-
() => factory.createCredential('did:CREDENTIAL_ID')
|
|
128
|
-
).to.throw('Parameter "holder" is required')
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
})
|
package/src/Schema.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { pick, cloneDeep } = require('lodash')
|
|
4
|
-
const validateId = require('./helpers/validateId')
|
|
5
|
-
const normalizeRequired = require('./helpers/normalizeRequired')
|
|
6
|
-
const normalizeProperties = require('./helpers/normalizeProperties')
|
|
7
|
-
const getLinkedDataContext = require('./ld/getLinkedDataContext')
|
|
8
|
-
const removeRequiredAndDefault = require('./helpers/removeRequiredAndDefault')
|
|
9
|
-
|
|
10
|
-
const UNDEFINED_SCHEMA_ID = 'UNDEFINED_SCHEMA_ID'
|
|
11
|
-
|
|
12
|
-
class Schema {
|
|
13
|
-
constructor(source = {}, id = UNDEFINED_SCHEMA_ID, url) {
|
|
14
|
-
this._id = id
|
|
15
|
-
this._url = url
|
|
16
|
-
this._source = source instanceof Schema ? source.source : source
|
|
17
|
-
|
|
18
|
-
if (url) {
|
|
19
|
-
validateId('url', url)
|
|
20
|
-
|
|
21
|
-
this._source.type = { required: true, type: 'string', default: id }
|
|
22
|
-
|
|
23
|
-
if (this._source.id) {
|
|
24
|
-
this._source.id = { required: true, type: 'string', format: 'url' }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const uri =
|
|
28
|
-
(url.endsWith('/') || url.endsWith('#')) ? `${url}${id}` : `${url}#${id}`
|
|
29
|
-
|
|
30
|
-
this._linkedDataType = {
|
|
31
|
-
'@id': uri,
|
|
32
|
-
'@context': getLinkedDataContext(this._source, url)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
normalizeProperties(this._source)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get id() {
|
|
40
|
-
return this._id
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get url() {
|
|
44
|
-
return this._url
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get source() {
|
|
48
|
-
return cloneDeep(this._source)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get jsonSchema() {
|
|
52
|
-
if (this._source.enum) {
|
|
53
|
-
return {
|
|
54
|
-
id: this._id,
|
|
55
|
-
...this.source
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const jsonSchema = {
|
|
60
|
-
id: this._id,
|
|
61
|
-
type: 'object',
|
|
62
|
-
properties: this.source
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
normalizeRequired(jsonSchema)
|
|
66
|
-
|
|
67
|
-
return jsonSchema
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get linkedDataType() {
|
|
71
|
-
return this._linkedDataType
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
clone(id) {
|
|
75
|
-
return new Schema(this.source, id)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
pure(id) {
|
|
79
|
-
const { properties: source } = removeRequiredAndDefault({ properties: this.source })
|
|
80
|
-
return new Schema(source, id)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
only(propertyNames, id) {
|
|
84
|
-
const source = pick(this.source, propertyNames)
|
|
85
|
-
return new Schema(source, id)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
extend(properties, id) {
|
|
89
|
-
return new Schema({ ...this.source, ...properties }, id)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
wrap(propertyName, options = { required: true }, id) {
|
|
93
|
-
const source = {
|
|
94
|
-
[propertyName]: {
|
|
95
|
-
properties: this.source,
|
|
96
|
-
...options
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return new Schema(source, id)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = Schema
|
package/src/Schema.spec.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { load } = require('js-yaml')
|
|
4
|
-
const { Schema } = require('src')
|
|
5
|
-
const { expect } = require('chai')
|
|
6
|
-
const { readFileSync } = require('fs')
|
|
7
|
-
|
|
8
|
-
const loadSync = (yamlPath) => {
|
|
9
|
-
const id = yamlPath.split('.')[0].split('/').reverse()[0]
|
|
10
|
-
const source = load(readFileSync(yamlPath))
|
|
11
|
-
|
|
12
|
-
return new Schema(source, id)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('Schema', () => {
|
|
16
|
-
describe('Schema.constructor(source = {}, id = UNDEFINED_SCHEMA_ID)', () => {
|
|
17
|
-
it('creates empty schema with default id', () => {
|
|
18
|
-
const schema = new Schema()
|
|
19
|
-
|
|
20
|
-
expect(schema.id).to.equal('UNDEFINED_SCHEMA_ID')
|
|
21
|
-
expect(schema.source).to.be.empty
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('extends schema properties with types', () => {
|
|
25
|
-
const schema = loadSync('examples/Profile.yaml')
|
|
26
|
-
|
|
27
|
-
expect(schema.source.name.type).to.eql('string')
|
|
28
|
-
expect(schema.source.gender.type).to.eql('string')
|
|
29
|
-
expect(schema.source.tags.items.type).to.eql('string')
|
|
30
|
-
expect(schema.source.favoriteItems.type).to.eql('array')
|
|
31
|
-
expect(schema.source.favoriteItems.items.type).to.not.exist
|
|
32
|
-
expect(schema.source.locations.type).to.eql('array')
|
|
33
|
-
expect(schema.source.locations.items.type).to.eql('object')
|
|
34
|
-
expect(schema.source.locations.items.properties.name.type).to.eql('string')
|
|
35
|
-
expect(schema.source.locations.items.properties.address.type).to.eql('object')
|
|
36
|
-
expect(schema.source.locations.items.properties.address.properties.zip.type).to.eql('string')
|
|
37
|
-
expect(schema.source.locations.items.properties.address.properties.city.type).to.eql('string')
|
|
38
|
-
expect(schema.source.locations.items.properties.address.properties.country.type).to.eql('string')
|
|
39
|
-
expect(schema.source.locations.items.properties.address.properties.addressLine1.type).to.eql('string')
|
|
40
|
-
expect(schema.source.locations.items.properties.address.properties.addressLine2.type).to.eql('string')
|
|
41
|
-
|
|
42
|
-
const stringEnumSchema = new Schema({ enum: [ 'L', 'M', 'S'] }, 'Size')
|
|
43
|
-
|
|
44
|
-
expect(stringEnumSchema.source.type).to.eql('string')
|
|
45
|
-
|
|
46
|
-
const numbersEnumSchema = new Schema({ enum: [ 1, 2, 3 ], type: 'number' }, 'Points')
|
|
47
|
-
|
|
48
|
-
expect(numbersEnumSchema.source.type).to.eql('number')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('creates schema from other schemas source', () => {
|
|
52
|
-
const entitySchema = new Schema({ name: { type: 'string' } }, 'Entity')
|
|
53
|
-
|
|
54
|
-
const schema = new Schema(entitySchema, 'EntityClone')
|
|
55
|
-
|
|
56
|
-
expect(schema.id).to.equal('EntityClone')
|
|
57
|
-
expect(schema.source).to.deep.equal(entitySchema.source)
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('.pure(id)', () => {
|
|
62
|
-
it('returns schema without required and default attributes', () => {
|
|
63
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
64
|
-
const updateProfileSchema = profileSchema.pure('UpdateProfile')
|
|
65
|
-
|
|
66
|
-
expect(updateProfileSchema.id).to.eql('UpdateProfile')
|
|
67
|
-
|
|
68
|
-
const { source } = updateProfileSchema
|
|
69
|
-
|
|
70
|
-
expect(source.name.required).to.not.exist
|
|
71
|
-
expect(source.gender.default).to.not.exist
|
|
72
|
-
expect(source.contactDetails.required).to.not.exist
|
|
73
|
-
expect(source.contactDetails.properties.email.required).to.not.exist
|
|
74
|
-
expect(source.contactDetails.properties.mobileNumber.default).to.not.exist
|
|
75
|
-
expect(source.locations.items.properties.name.require).to.not.exist
|
|
76
|
-
expect(source.locations.items.properties.address.properties.country.required).to.not.exist
|
|
77
|
-
expect(source.locations.items.properties.address.properties.country.default).to.not.exist
|
|
78
|
-
expect(source.locations.items.properties.address.properties.city.required).to.not.exist
|
|
79
|
-
expect(source.locations.items.properties.address.properties.addressLine1.required).to.not.exist
|
|
80
|
-
expect(source.locations.items.properties.address.properties.addressLine2.required).to.not.exist
|
|
81
|
-
expect(source.locations.items.properties.address.properties.zip.required).to.not.exist
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
describe('.clone(id)', () => {
|
|
86
|
-
it('returns schema clone', () => {
|
|
87
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
88
|
-
|
|
89
|
-
const schema = profileSchema.clone('ProfileClone')
|
|
90
|
-
expect(schema.id).to.eql('ProfileClone')
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
describe('.only(propertyNames, id)', () => {
|
|
95
|
-
it('returns schema with only requested properties', () => {
|
|
96
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
97
|
-
|
|
98
|
-
const schema = profileSchema.only([ 'name', 'gender' ], 'ProfileClone')
|
|
99
|
-
expect(schema.id).to.eql('ProfileClone')
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
describe('.extend(properties, id)', () => {
|
|
104
|
-
it('returns schema extended with specified properties', () => {
|
|
105
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
106
|
-
|
|
107
|
-
const documentSource = {
|
|
108
|
-
createdAt: {
|
|
109
|
-
type: 'string',
|
|
110
|
-
format: 'date-time',
|
|
111
|
-
required: true
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const profileDocumentSchema = profileSchema.extend(documentSource, 'ProfileDocument')
|
|
116
|
-
|
|
117
|
-
expect(profileDocumentSchema.id).to.eql('ProfileDocument')
|
|
118
|
-
expect(profileDocumentSchema.source.createdAt).to.exist
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
describe('.wrap(propertyName, options = { required: true }, id)', () => {
|
|
123
|
-
it('returns schema that wraps source schema with object property', () => {
|
|
124
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
125
|
-
|
|
126
|
-
const dataSchema = profileSchema.wrap('data')
|
|
127
|
-
expect(dataSchema.id).to.eql('UNDEFINED_SCHEMA_ID')
|
|
128
|
-
expect(dataSchema.source.data).to.exist
|
|
129
|
-
expect(dataSchema.source.data.required).to.exist
|
|
130
|
-
|
|
131
|
-
const alternativeDataSchema = profileSchema.wrap('data', { default: {} }, 'ResponseOutput')
|
|
132
|
-
expect(alternativeDataSchema.id).to.eql('ResponseOutput')
|
|
133
|
-
expect(alternativeDataSchema.source.data).to.exist
|
|
134
|
-
expect(alternativeDataSchema.source.data.default).to.exist
|
|
135
|
-
expect(alternativeDataSchema.source.data.required).to.not.exist
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('.jsonSchema', () => {
|
|
140
|
-
it('returns json schema for enum type', () => {
|
|
141
|
-
const source = {
|
|
142
|
-
type: 'string',
|
|
143
|
-
enum: [ 'L', 'M', 'S' ]
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const sizeSchema = new Schema(source, 'Size')
|
|
147
|
-
|
|
148
|
-
expect(sizeSchema.jsonSchema.id).to.eql('Size')
|
|
149
|
-
expect(sizeSchema.jsonSchema.type).to.eql('string')
|
|
150
|
-
expect(sizeSchema.jsonSchema.enum).to.deep.equal(source.enum)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('returns json schema with normalized required attributes', () => {
|
|
154
|
-
const profileSchema = loadSync('examples/Profile.yaml')
|
|
155
|
-
|
|
156
|
-
const { jsonSchema } = profileSchema
|
|
157
|
-
|
|
158
|
-
expect(jsonSchema).to.have.property('type', 'object')
|
|
159
|
-
expect(jsonSchema).to.have.property('properties')
|
|
160
|
-
expect(jsonSchema.required).to.deep.equal([ 'name', 'contactDetails' ])
|
|
161
|
-
expect(jsonSchema.properties.contactDetails.required).to.deep.equal([ 'email' ])
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('returns json schema without required attributes', () => {
|
|
165
|
-
const schema = new Schema({ name: { type: 'string' } }, 'Entity')
|
|
166
|
-
|
|
167
|
-
const { jsonSchema } = schema
|
|
168
|
-
|
|
169
|
-
expect(jsonSchema.required).to.not.exist
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
})
|
package/src/ValidationError.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { pick } = require('lodash')
|
|
4
|
-
|
|
5
|
-
class ValidationError extends Error {
|
|
6
|
-
constructor(schemaId, invalidObject, validationErrors) {
|
|
7
|
-
super(`"${schemaId}" validation failed`)
|
|
8
|
-
|
|
9
|
-
this._object = invalidObject
|
|
10
|
-
this._schemaId = schemaId
|
|
11
|
-
this._validationErrors = validationErrors.map(error => pick(error, [
|
|
12
|
-
'path',
|
|
13
|
-
'code',
|
|
14
|
-
'params',
|
|
15
|
-
'message',
|
|
16
|
-
'schemaId'
|
|
17
|
-
]))
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
toJSON() {
|
|
21
|
-
return {
|
|
22
|
-
code: this.constructor.name,
|
|
23
|
-
object: this._object,
|
|
24
|
-
message: this.message,
|
|
25
|
-
schemaId: this._schemaId,
|
|
26
|
-
validationErrors: this._validationErrors
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
module.exports = ValidationError
|
package/src/Validator.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { keyBy, groupBy } = require('lodash')
|
|
4
|
-
const ZSchema = require('z-schema')
|
|
5
|
-
const cleanupNulls = require('./helpers/cleanupNulls')
|
|
6
|
-
const getReferenceIds = require('./helpers/getReferenceIds')
|
|
7
|
-
const ValidationError = require('./ValidationError')
|
|
8
|
-
const cleanupAttributes = require('./helpers/cleanupAttributes')
|
|
9
|
-
const nullifyEmptyValues = require('./helpers/nullifyEmptyValues')
|
|
10
|
-
const normalizeAttributes = require('./helpers/normalizeAttributes')
|
|
11
|
-
|
|
12
|
-
class Validator {
|
|
13
|
-
constructor(schemas = []) {
|
|
14
|
-
if (schemas.length === 0) {
|
|
15
|
-
throw new Error('No schemas provided')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const groupsById = groupBy(schemas, 'id')
|
|
19
|
-
|
|
20
|
-
for (const id in groupsById) {
|
|
21
|
-
const schemas = groupsById[id]
|
|
22
|
-
const hasDuplicates = schemas.length > 1
|
|
23
|
-
|
|
24
|
-
if (hasDuplicates) {
|
|
25
|
-
throw new Error(`Multiple "${id}" schemas provided`)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
this._engine = new ZSchema({
|
|
30
|
-
reportPathAsArray: false,
|
|
31
|
-
ignoreUnknownFormats: true,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const jsonSchemas = schemas.map(({ jsonSchema }) => jsonSchema)
|
|
35
|
-
const isValid = this._engine.validateSchema(jsonSchemas)
|
|
36
|
-
|
|
37
|
-
if (!isValid) {
|
|
38
|
-
const json = JSON.stringify(this._engine.lastReport.errors, null, 2)
|
|
39
|
-
throw new Error(`Schemas validation failed:\n${json}`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
this._schemasMap = keyBy(schemas, 'id')
|
|
43
|
-
this._jsonSchemasMap = keyBy(jsonSchemas, 'id')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
validate(object, schemaId, shouldNullifyEmptyValues = false, shouldCleanupNulls = false) {
|
|
47
|
-
const jsonSchema = this._jsonSchemasMap[schemaId]
|
|
48
|
-
|
|
49
|
-
if (!jsonSchema) {
|
|
50
|
-
throw new Error(`Schema "${schemaId}" not found`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const objectJson = JSON.stringify(object)
|
|
54
|
-
let result = JSON.parse(objectJson)
|
|
55
|
-
|
|
56
|
-
if (shouldCleanupNulls) {
|
|
57
|
-
result = cleanupNulls(result)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
// NOTE: Drop attributes from objects that are not defined in schema.
|
|
62
|
-
// This is bad for FE developers, as they continue to send some
|
|
63
|
-
// trash to endpoints, but good for integrations with third party
|
|
64
|
-
// services, e.g. Telegram, when you do not want to define schema
|
|
65
|
-
// for the full payload. This method currently fails for cases when
|
|
66
|
-
// attribute is defined as object or array in schema, but value is
|
|
67
|
-
// a string. In this case validation method below would catch that.
|
|
68
|
-
cleanupAttributes(result, jsonSchema, this._jsonSchemasMap)
|
|
69
|
-
|
|
70
|
-
// NOTE: Normalize method helps to integrate objects built from URLs,
|
|
71
|
-
// where types are not defined, e.g. booleans are '1', 'yes' string
|
|
72
|
-
// or numbers are '1', '2'... strings.
|
|
73
|
-
normalizeAttributes(result, jsonSchema, this._jsonSchemasMap)
|
|
74
|
-
|
|
75
|
-
// eslint-disable-next-line no-unused-vars
|
|
76
|
-
} catch (error) {
|
|
77
|
-
// NOTE: Skip errors in cleanup and normalize attributes methods,
|
|
78
|
-
// validation fails for objects with invalid value types.
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const isValid = this._engine.validate(result, jsonSchema)
|
|
83
|
-
|
|
84
|
-
if (isValid) {
|
|
85
|
-
return result
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let validationErrors = this._engine.getLastErrors()
|
|
89
|
-
|
|
90
|
-
if (!shouldNullifyEmptyValues) {
|
|
91
|
-
throw new ValidationError(schemaId, result, validationErrors)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const [ updatedResult, updatedValidationErrors ] = nullifyEmptyValues(result, validationErrors)
|
|
95
|
-
|
|
96
|
-
const hasValidationErrors = updatedValidationErrors.length > 0
|
|
97
|
-
|
|
98
|
-
if (hasValidationErrors) {
|
|
99
|
-
throw new ValidationError(schemaId, result, updatedValidationErrors)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return updatedResult
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
normalize(object, schemaId) {
|
|
106
|
-
const jsonSchema = this._jsonSchemasMap[schemaId]
|
|
107
|
-
|
|
108
|
-
if (!jsonSchema) {
|
|
109
|
-
throw new Error(`Schema "${schemaId}" not found`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const result = JSON.parse(JSON.stringify(object))
|
|
113
|
-
normalizeAttributes(result, jsonSchema, this._jsonSchemasMap)
|
|
114
|
-
|
|
115
|
-
return result
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
get schemasMap() {
|
|
119
|
-
return this._schemasMap
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
getReferenceIds(schemaId) {
|
|
123
|
-
const schema = this._schemasMap[schemaId]
|
|
124
|
-
return getReferenceIds(schema, this._schemasMap)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = Validator
|