From 5003e952a6d6b6789953d33a80389a6411c9b808 Mon Sep 17 00:00:00 2001 From: liquidg3 Date: Mon, 11 May 2020 10:25:58 -0600 Subject: [PATCH] minor: hard coded version of tabs, removed extra prism --- errors/index.md | 42 ++++++++++-- index.html | 6 +- schemas/index.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++- spruce.css | 8 ++- 4 files changed, 215 insertions(+), 15 deletions(-) diff --git a/errors/index.md b/errors/index.md index 13f8f985..9789cff8 100644 --- a/errors/index.md +++ b/errors/index.md @@ -1,5 +1,5 @@ # Errors -Build errors that solve problems. +Flexible and informative error reporting. ```bash # Create a new error definition @@ -138,6 +138,8 @@ export interface IYouMustBe18OrOlderErrorOptions extends SchemaDefinitionValues< Once you create your error definition, you'll want to edit it. Jump into `./src/errors/youMustBe18OrOlder.definition.ts` and update the fields. +Since errors are defined using schemas, you can learn a lot more by reading the [Schema docs](/schemas/index.md). + ```ts // ./src/errors/youMustBe18OrOlder.definition.**ts** @@ -151,7 +153,8 @@ const genericDefinition = buildErrorDefinition({ fields: { suppliedBirthDate: { type: FieldType.Date, - label: 'Supplied birth date' + label: 'Supplied birth date', + isRequired: true }, ipAddress: { type: FieldType.Text, @@ -174,25 +177,52 @@ to ensure all the types are updated too. ## Throwing an error ```ts +// import Error class and error codes import SpruceError from '../errors/SpruceError' import { ErrorCode } from '#spruce/errors/codes.types' throw new SpruceError({ code: ErrorCode.YouMustBe18OrOlder, - suppliedBirthDate: someUserSuppliedInput + suppliedBirthDate: someUserSuppliedInput // only field that is required based on definition }) ``` ## Catching an error + +Catching an error is pretty simple, but since `SpruceError` was designed to be chained, you can throw more relevant errors while passing through ```ts import { ErrorCode } from '#spruce/errors/codes.types' try { assertOldEnough(someUserSuppliedInput) } catch (err) { - if (err instanceof SpruceError && err.options.code === ErrorCode) { - console.log(err.friendlyMessage()) // + // after checking if the error is a SpruceError you can check the error code + if (err instanceof SpruceError && err.options.code === ErrorCode.YouMustBe18OrOlder) { + console.log(err.friendlyMessage()) + console.log(err.options.suppliedBirthDate); // Typescript knows suppliedBirthDate is required console.log(err.options) // { code: 'YouMustBe18OrOlder', suppliedBirthDate: 1/1/10'} } + + // OR you can do a switch for the code + if (err instanceof SpruceError) { + switch(err.options.code) { + case ErrorCode.YouMustBe18OrOlder: + console.log(`NOT OLD ENOUGH: ${err.options.ipAddress ? `IP Address: ${err.options.ipAddress}` : ''}`) + + // chain this error with a new one with a more helpful message + throw new SpruceError({ + code: ErrorCode.SignUpFailed, + originalError: err, + friendlyReason: 'Uh oh! I couldn\'t sign you up, check the following errors for more details.' + }) + break + case ErrorCode.SomeOtherError: + console.log('Error!!') + break + } + } } -``` \ No newline at end of file +``` +## Chaining errors + +## Serializing errors diff --git a/index.html b/index.html index d14cdde4..f25648ba 100644 --- a/index.html +++ b/index.html @@ -145,7 +145,7 @@ greedy: !0, inside: null, }, - keyword: /\b(?:abstract|as|asserts|async|await|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|null|of|package|private|protected|public|readonly|return|require|set|static|super|switch|this|throw|try|type|typeof|undefined|var|void|while|with|yield)\b/, + keyword: /\b(?:abstract|as|asserts|async|await|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|null|of|package|private|protected|public|readonly|return|require|set|static|super|switch|this|throw|try|type[!:]|FieldType\..*|typeof|undefined|var|void|while|with|yield)\b/, builtin: /\b(?:string|Function|any|number|boolean|Array|symbol|console|Promise|unknown|never)\b/, } )), @@ -185,11 +185,9 @@ - + - - diff --git a/schemas/index.md b/schemas/index.md index 5e1a0984..b808a4c6 100644 --- a/schemas/index.md +++ b/schemas/index.md @@ -87,7 +87,6 @@ const shirtDefinition: ISchemaDefinition = buildSchemaDefinition({ // always export the definition as the default from a .definition.ts file export default shirtDefinition - ``` #### ** Validate ** @@ -151,15 +150,184 @@ phoneNumberField.validate('232324234234234') // throws FieldValidationError ## Building your first schema +Lets start by defining a Ball. From that, we'll create a sub-schemas and a related schema. + +```bash +spruce schema:create "Ball" +spruce schema:create "Soccer ball" +spruce schema:create "Vendor" +``` + +## Updating definitions + +We'll start by defining our Vendor schema since it's required for the ball schemas. + +```ts +// ./src/schemas/vendor.definition.ts + +import Schema, { FieldType, buildSchemaDefinition } from '@sprucelabs/schema' + +const vendorDefinition = buildSchemaDefinition({ + id: 'vendor', + name: 'Vendor', + description: 'A vendor is a company that makes balls', + fields: { + id: { + type: FieldType.Id, + label: 'Id', + }, + name: { + type: FieldType.Text, + label: 'Weight', + hint: 'In ounces', + options: { + min: 0 + } + } + } +}) + +export default vendorDefinition + + +``` +Lets define the fields for the "base" definition that all balls will mixin. +```ts +// ./src/schemas/ball.definition.ts + +import Schema, { FieldType, buildSchemaDefinition } from '@sprucelabs/schema' + +// import the related definition +import vendorDefinition from './vendor.definition' + +const ballDefinition = buildSchemaDefinition({ + id: 'ball', + name: 'Ball', + description: 'All balls extend this ball', + fields: { + id: { + type: FieldType.Id, + label: 'Id', + }, + weightOz: { + type: FieldType.Number, + label: 'Weight', + hint: 'In ounces', + options: { + min: 0 + } + }, + color: { + type: FieldType.Select, + label: 'Color', + options: { + choices: [ + { value: 'red', label: 'Red'} + { value: 'green', label: 'Green'}, + { value: 'blue', label: 'Blue'} + ] + } + }, + vendor: { + type: FieldType.Schema, + label: 'Vendor', + isRequired: true, + options: { + schema: vendorDefinition + } + } + } +}) + +export default ballDefinition + +``` + +Now lets define our sub-schema that will extend a Ball. +```ts + +// ./src/schemas/soccerBall.definition.ts + +import Schema, { FieldType, buildSchemaDefinition } from '@sprucelabs/schema' + +// import the "parent" schema definition +import ballDefinition from './ball.definition' + +const soccerBallDefinition = buildSchemaDefinition({ + id: 'soccerBall', + name: 'Soccer ball', + description: 'A ball that is kicked.', + fields: { + // mixin all ball fields + ...ballDefinition.fields, + color: { + // preserve color field props + ...ballDefinition.fields.color, + options: { + // preserve color field options + ...ballDefinition.fields.color.options, + choices: [ + // preserve color choices but add in a new one + ...ballDefinition.fields.color.options.choices, + { value: 'blackAndWhite', label: 'Black and white'} + ] + } + } + } +}) + +export default soccerBallDefinition + +``` + +After you are done editing your definitions you'll need to sync the type files. + +```bash +spruce schema:sync +``` + +## Extending definitions +There is no concept of inheritance in schemas and their definitions. Instead, as you see in the examples above, schemas use a mixin approach with the `...spread` operator. -## Using your definitions +## Using your definitions (instantiating schemas) I make it really easy to import a definition from anywhere. `import { SpruceSchemas } from '#spruce/schemas/schemas.types'` -All your definitions will be attached under `SpruceSchemas.local`. +All your definitions will are located at `SpruceSchemas.Local`. + +```ts +import { SpruceSchemas } from '#spruce/schemas/schemas.types' +import Schema from '@sprucelabs/schema' + +// create a vendor and pass values on instantiation +const vendor = new Schema(SpruceSchemas.Local.Vendor, { name: 'Adidas' }) + +// create a soccer ball +const ball = new Schema(SpruceSchemas.Local.SoccerBall) + +// set values +ball.set('vendor', vendor) +ball.set('color', 'blackAndWhite') + +// should pass +if (ball.isValid()) { + console.log('great work') +} + +``` + +## Schema class API + +The `Schema` class uses definitions for data validation and normalization. Putting a `Schema` behind your REST, GQL, or Mercury events ensures world class validation and error reporting in an instant. + +* `constructor` + * `definition`: [ISchemaDefinition](https://github.com/sprucelabsai/spruce-schema/blob/dev/src/schema.types.ts#L42) - the definition to base this schema on. You can pass it as an object literal or pull it off `SpruceSchemas` + * `values`: [SchemaDefinitionPartialValues]() + + ## Field Types diff --git a/spruce.css b/spruce.css index c42630e4..ef792302 100644 --- a/spruce.css +++ b/spruce.css @@ -39,8 +39,12 @@ --code-theme-punctuation: #F6F6F4; --code-theme-function: #CCF1DF; --code-theme-operator: #FEF6D8; - --code-theme-selector: #c6c6c6; - --code-inline-border-radius: 6px; + --code-theme-selector: #c6c6c6; + + /* inline code */ + --code-inline-border-radius: 6px; + --code-inline-background: var(--theme-color) !important; + --code-inline-color: var(--dark-theme-color) !important; /* panels */ --docsify-example-panels-left-panel-width: 50%;