Skip to content

Commit

Permalink
break(object): add additionalProperties: false if properties
Browse files Browse the repository at this point in the history
Closes #5
  • Loading branch information
lukeed committed Aug 7, 2024
1 parent 4a27c67 commit bc4dc26
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 21 deletions.
60 changes: 54 additions & 6 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,13 +417,59 @@ describe('Object', () => {
});
});

describe('additionalProperties', () => {
it('should NOT be added for object w/o properties', () => {
let output = t.object();

assertEquals(output, {
type: 'object',
properties: undefined,
});
});

it('should be added for object w/ defined properties', () => {
let output = t.object({
name: t.string(),
});

assertEquals(output, {
type: 'object',
additionalProperties: false, // <<
required: ['name'],
properties: {
name: {
type: 'string',
},
},
});
});

it('should allow override via options', () => {
let output = t.object({
name: t.string(),
}, {
additionalProperties: true,
});

assertEquals(output, {
type: 'object',
additionalProperties: true, // <<
required: ['name'],
properties: {
name: {
type: 'string',
},
},
});
});
});

it('should build "properties" property', () => {
let output = t.object({
name: t.string(),
age: t.integer(),
}, {
description: 'person',
additionalProperties: false,
examples: [{
name: 'lukeed',
age: 123,
Expand Down Expand Up @@ -464,6 +510,7 @@ describe('Object', () => {

assertEquals(output, {
type: 'object',
additionalProperties: false,
properties: {
name: { type: 'string' },
age: { type: 'integer' },
Expand All @@ -477,11 +524,6 @@ describe('Object', () => {
name: t.string(),
age: t.optional(t.integer()),
});
// TODO: Make ^ this:
// t.Object<{
// name: t.String<string>;
// age?: t.Integer<number>;
// }>

// NOTE: age has OPTIONAL symbol
// This is dropped natively in JSON
Expand All @@ -491,13 +533,19 @@ describe('Object', () => {

assertEquals(copy, {
type: 'object',
additionalProperties: false,
properties: {
name: { type: 'string' },
age: { type: 'integer' },
},
required: ['name'], // <<
});
});

it('should default "additionalProperties" to false', () => {
let output = t.object();
type X = t.Infer<typeof output>;
});
});

// ---
Expand Down
74 changes: 61 additions & 13 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,14 @@ type _object<T extends Properties> = Annotations & {
* > Use {@link optional} to mark individual properties as optional, or supply
* > your own `required` attribute.
*
* > [!IMPORTANT]
* > By default, `additionalProperties: false` is added when `properties` are defined.
* > This restricts the object's definition to **only** accept the property fields you've defined.
* > Otherwise, when no `properties` are defined — for example, `t.object()` — then
* > `additionalProperties: false` **is not** added, which allows for extra
* > properties. You may explicitly control this setting via
* > `options.additionalProperties` at any time.
*
* @example Basic Usage
* ```ts
* let pet = t.object({
Expand All @@ -573,11 +581,47 @@ type _object<T extends Properties> = Annotations & {
* name: t.string(),
* age: t.integer(),
* }, {
* additionalProperties: false,
* description: 'the Pet schema',
* // ...
* });
* ```
*
* @example Additional Properties
* ```ts
* // Accept any object
* // AKA: any object w/ any properties
* let a = t.object();
* //-> { type: "object" }
*
* // Accept a specific object
* // AKA: only specified properites
* let b = t.object({
* name: t.string(),
* });
* //-> {
* //-> type: "object",
* //-> additionalProperties: false,
* //-> required: ["name"],
* //-> properties: {
* //-> name: { type: "string" }
* //-> }
* //-> }
*
* // Accept a specific object
* // ... but allow extra properties
* let c = t.object({
* name: t.string(),
* }, {
* additionalProperties: true,
* });
* //-> {
* //-> type: "object",
* //-> additionalProperties: true,
* //-> required: ["name"],
* //-> properties: {
* //-> name: { type: "string" }
* //-> }
* //-> }
*/
function _object<
P extends Properties,
Expand All @@ -592,19 +636,23 @@ function _object<
properties,
} as T;

if (properties && !o.required) {
let k: keyof P;
let arr: (keyof P)[] = [];
for (k in properties) {
if (properties[k][OPTIONAL]) {
// NOTE: delete = deopt
properties[k][OPTIONAL] = undefined;
} else {
arr.push(k);
if (properties) {
o.additionalProperties ||= false;

if (!o.required) {
let k: keyof P;
let arr: (keyof P)[] = [];
for (k in properties) {
if (properties[k][OPTIONAL]) {
// NOTE: delete = deopt
properties[k][OPTIONAL] = undefined;
} else {
arr.push(k);
}
}
if (arr.length > 0) {
o.required = arr;
}
}
if (arr.length > 0) {
o.required = arr;
}
}

Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# tschema [![CI](https://github.com/lukeed/tschema/workflows/CI/badge.svg)](https://github.com/lukeed/tschema/actions?query=workflow%3ACI) [![licenses](https://licenses.dev/b/npm/tschema)](https://licenses.dev/npm/tschema)

> A tiny (408b) utility to build [JSON schema](https://json-schema.org/understanding-json-schema/reference) types.
> A tiny (425b) utility to build [JSON schema](https://json-schema.org/understanding-json-schema/reference) types.
## Install

Expand Down
2 changes: 1 addition & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join, resolve } from 'jsr:@std/path@^1.0';
import oxc from 'npm:oxc-transform@^0.23';

const bytes = 408;
const bytes = 425;

const version = Deno.args[0];
console.log('? version:', version);
Expand Down

0 comments on commit bc4dc26

Please sign in to comment.