Skip to content

Commit

Permalink
Add S.raw (#56)
Browse files Browse the repository at this point in the history
* S.raw draft #55

* Add node 12.x

* Switch default to node 12.14

* Fix types

* turn on noEmit again

* fix typo

* Uptated to the latest version of AJV and added AJV custom keywords package

* Added .raw method on each Schema

* Cleanup and doc

* Fix coverage and update doc

* Add more tests about S.raw

* Extracted Raw as a Schema

* Add an integration test for AJV and $data

* Add a check that the Fragment is an object
  • Loading branch information
aboutlo authored Jan 24, 2020
1 parent 83c3885 commit f59ab9c
Show file tree
Hide file tree
Showing 28 changed files with 1,040 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v10.13.0
v12.14.0
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ language: node_js

node_js:
- "13"
- "11"
- "12
- "10"
- "8"
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,16 @@ const schema = S.object()
.enum(Object.values(ROLES))
.default(ROLES.USER)
)
.prop(
'birthday',
S.raw({ type: 'string', format: 'date', formatMaximum: '2020-01-01' }) // formatMaximum is an AJV custom keywords
)
.definition(
'address',
S.object()
.id('#address')
.prop('line1', S.string())
.prop('line2', S.anyOf([S.string(), S.null()]))
.prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable
.prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger nullable
.prop('country', S.string())
.prop('city', S.string())
.prop('zipcode', S.string())
Expand All @@ -81,9 +85,6 @@ Schema generated:
"$id": "#address",
"properties": {
"line1": {
"type": "string"
},
"line2": {
"anyOf": [
{
"type": "string"
Expand All @@ -93,6 +94,10 @@ Schema generated:
}
]
},
"line2": {
"type": "string",
"nullable": true
},
"country": {
"type": "string"
},
Expand All @@ -119,10 +124,15 @@ Schema generated:
"type": "string",
"minLength": 8
},
"birthday": {
"type": "string",
"format": "date",
"formatMaximum": "2020-01-01"
},
"role": {
"type": "string",
"enum": ["ADMIN", "USER"],
"default": "USER",
"type": "string"
"default": "USER"
},
"address": {
"$ref": "#address"
Expand Down
69 changes: 69 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ validation succeeds against this keyword if the instance also successfully valid
<dd><p>When &quot;if&quot; is present, and the instance fails to validate against its subschema,
then validation succeeds against this keyword if the instance successfully validates against this keyword&#39;s subschema.</p>
</dd>
<dt><a href="#raw">raw(fragment)</a> ⇒ <code><a href="#BaseSchema">BaseSchema</a></code></dt>
<dd><p>Because the differences between JSON Schemas and Open API (Swagger)
it can be handy to arbitrary modify the schema injecting a fragment</p>
<ul>
<li>Examples:</li>
<li>S.number().raw({ nullable:true })</li>
<li>S.string().format(&#39;date&#39;).raw({ formatMaximum: &#39;2020-01-01&#39; })</li>
</ul>
</dd>
<dt><a href="#valueOf">valueOf()</a> ⇒ <code><a href="#object">object</a></code></dt>
<dd><p>It returns all the schema values</p>
</dd>
Expand Down Expand Up @@ -147,6 +156,15 @@ then validation succeeds against this keyword if the instance successfully valid
<dt><a href="#mixed">mixed(types)</a> ⇒ <code><a href="#MixedSchema">MixedSchema</a></code></dt>
<dd><p>A mixed schema is the union of multiple types (e.g. [&#39;string&#39;, &#39;integer&#39;]</p>
</dd>
<dt><a href="#raw">raw(fragment)</a> ⇒ <code><a href="#BaseSchema">BaseSchema</a></code></dt>
<dd><p>Because the differences between JSON Schemas and Open API (Swagger)
it can be handy to arbitrary modify the schema injecting a fragment</p>
<ul>
<li>Examples:</li>
<li>S.raw({ nullable:true, format: &#39;date&#39;, formatMaximum: &#39;2020-01-01&#39; })</li>
<li>S.string().format(&#39;date&#39;).raw({ formatMaximum: &#39;2020-01-01&#39; })</li>
</ul>
</dd>
<dt><a href="#IntegerSchema">IntegerSchema([options])</a> ⇒ <code><a href="#NumberSchema">NumberSchema</a></code></dt>
<dd><p>Represents a NumberSchema.</p>
</dd>
Expand Down Expand Up @@ -225,6 +243,9 @@ Note the property name that the schema is testing will always be a string.</p>
There are no restrictions placed on the values within the array.</p>
<p><a href="reference">https://json-schema.org/latest/json-schema-validation.html#rfc.section.9</a></p>
</dd>
<dt><a href="#RawSchema">RawSchema(schema)</a> ⇒ <code>FluentSchema</code></dt>
<dd><p>Represents a raw JSON Schema that will be parsed</p>
</dd>
<dt><a href="#StringSchema">StringSchema([options])</a> ⇒ <code><a href="#StringSchema">StringSchema</a></code></dt>
<dd><p>Represents a StringSchema.</p>
</dd>
Expand Down Expand Up @@ -589,6 +610,24 @@ then validation succeeds against this keyword if the instance successfully valid
| thenClause | [<code>BaseSchema</code>](#BaseSchema) | |
| elseClause | [<code>BaseSchema</code>](#BaseSchema) | [https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.6.1](reference) |

<a name="raw"></a>

## raw(fragment) ⇒ [<code>BaseSchema</code>](#BaseSchema)

Because the differences between JSON Schemas and Open API (Swagger)
it can be handy to arbitrary modify the schema injecting a fragment

- Examples:

* S.number().raw({ nullable:true })
* S.string().format('date').raw({ formatMaximum: '2020-01-01' })

**Kind**: global function

| Param | Type | Description |
| -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| fragment | [<code>string</code>](#string) | an arbitrary JSON Schema to inject [https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.3.3](reference) |

<a name="valueOf"></a>

## valueOf() ⇒ [<code>object</code>](#object)
Expand Down Expand Up @@ -699,6 +738,24 @@ A mixed schema is the union of multiple types (e.g. ['string', 'integer']
| ----- | ------------------------------------------------------------ |
| types | [<code>[ &#x27;Array&#x27; ].&lt;string&gt;</code>](#string) |

<a name="raw"></a>

## raw(fragment) ⇒ [<code>BaseSchema</code>](#BaseSchema)

Because the differences between JSON Schemas and Open API (Swagger)
it can be handy to arbitrary modify the schema injecting a fragment

- Examples:

* S.raw({ nullable:true, format: 'date', formatMaximum: '2020-01-01' })
* S.string().format('date').raw({ formatMaximum: '2020-01-01' })

**Kind**: global function

| Param | Type | Description |
| -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| fragment | [<code>string</code>](#string) | an arbitrary JSON Schema to inject [https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.3.3](reference) |

<a name="IntegerSchema"></a>

## IntegerSchema([options]) ⇒ [<code>NumberSchema</code>](#NumberSchema)
Expand Down Expand Up @@ -947,6 +1004,18 @@ There are no restrictions placed on the values within the array.
| name | [<code>string</code>](#string) |
| props | <code>FluentSchema</code> |

<a name="RawSchema"></a>

## RawSchema(schema) ⇒ <code>FluentSchema</code>

Represents a raw JSON Schema that will be parsed

**Kind**: global function

| Param | Type |
| ------ | ------------------- |
| schema | <code>Object</code> |

<a name="StringSchema"></a>

## StringSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"pre-commit": "lint-staged"
}
},
"jest": {
"coverageReporters": ["text", "lcovonly"]
},

"lint-staged": {
"linters": {
"*.{json,md,js,ts}": [
Expand All @@ -54,7 +58,8 @@
"doc": "jsdoc2md ./src/*.js > docs/API.md"
},
"devDependencies": {
"ajv": "^6.5.5",
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1",
"husky": "^1.1.3",
"jest": "^24.3.1",
"jsdoc-to-markdown": "^4.0.1",
Expand Down
13 changes: 13 additions & 0 deletions src/ArraySchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,18 @@ describe('ArraySchema', () => {
).toThrow("'maxItems' must be a integer")
})
})

describe('raw', () => {
it('allows to add a custom attribute', () => {
const schema = ArraySchema()
.raw({ customKeyword: true })
.valueOf()

expect(schema).toEqual({
type: 'array',
customKeyword: true,
})
})
})
})
})
17 changes: 17 additions & 0 deletions src/BaseSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
patchIdsWithParentId,
REQUIRED,
setAttribute,
setRaw,
setComposeType,
FLUENT_SCHEMA,
} = require('./utils')
Expand Down Expand Up @@ -361,6 +362,22 @@ const BaseSchema = (
})
},

/**
* Because the differences between JSON Schemas and Open API (Swagger)
* it can be handy to arbitrary modify the schema injecting a fragment
*
* * Examples:
* - S.number().raw({ nullable:true })
* - S.string().format('date').raw({ formatMaximum: '2020-01-01' })
*
* @param {string} fragment an arbitrary JSON Schema to inject
* {@link reference|https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.3.3}
* @returns {BaseSchema}
*/
raw: fragment => {
return setRaw({ schema, ...options }, fragment)
},

/**
* @private It returns the internal schema data structure
* @returns {object}
Expand Down
14 changes: 14 additions & 0 deletions src/BaseSchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,4 +570,18 @@ describe('BaseSchema', () => {
})
})
})

describe('raw', () => {
it('allows to add a custom attribute', () => {
const schema = BaseSchema()
.title('foo')
.raw({ customKeyword: true })
.valueOf()

expect(schema).toEqual({
title: 'foo',
customKeyword: true,
})
})
})
})
13 changes: 13 additions & 0 deletions src/BooleanSchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,17 @@ describe('BooleanSchema', () => {
.valueOf().properties.prop.type
).toEqual('boolean')
})

describe('raw', () => {
it('allows to add a custom attribute', () => {
const schema = BooleanSchema()
.raw({ customKeyword: true })
.valueOf()

expect(schema).toEqual({
type: 'boolean',
customKeyword: true,
})
})
})
})
4 changes: 3 additions & 1 deletion src/FluentSchema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface BaseSchema<T> {
readOnly: (isReadOnly?: boolean) => T
writeOnly: (isWriteOnly?: boolean) => T
isFluentSchema: boolean
raw: (fragment: any) => JSONSchema
}

export type TYPE =
Expand Down Expand Up @@ -102,7 +103,7 @@ export interface ArraySchema extends BaseSchema<ArraySchema> {
contains: (value: JSONSchema | boolean) => ArraySchema
uniqueItems: (boolean: boolean) => ArraySchema
minItems: (min: number) => ArraySchema
maxItems: (min: number) => ArraySchema
maxItems: (max: number) => ArraySchema
}

export interface ObjectSchema extends BaseSchema<ObjectSchema> {
Expand Down Expand Up @@ -148,6 +149,7 @@ export interface S extends BaseSchema<S> {
null: () => NullSchema
//FIXME LS we should return only a MixedSchema
mixed: <T>(types: TYPE[]) => MixedSchema<T> & any
raw: (fragment: any) => JSONSchema
}

declare var s: S
Expand Down
Loading

0 comments on commit f59ab9c

Please sign in to comment.