Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
add BrandSchema, getOption (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Mar 6, 2023
1 parent d07b0f1 commit c935ff2
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-bulldogs-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

add BrandSchema, getOption
13 changes: 13 additions & 0 deletions docs/modules/Parser.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Added in v1.0.0
- [decoding](#decoding)
- [decode](#decode)
- [decodeOrThrow](#decodeorthrow)
- [getOption](#getoption)
- [encoding](#encoding)
- [encode](#encode)
- [encodeOrThrow](#encodeorthrow)
Expand Down Expand Up @@ -100,6 +101,18 @@ export declare const decodeOrThrow: <A>(
Added in v1.0.0
## getOption
**Signature**
```ts
export declare const getOption: <A>(
schema: Schema<A>
) => (input: unknown, options?: AST.ParseOptions | undefined) => O.Option<A>
```
Added in v1.0.0
# encoding
## encode
Expand Down
17 changes: 14 additions & 3 deletions docs/modules/Schema.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Added in v1.0.0
- [startsWith](#startswith)
- [trimmed](#trimmed)
- [model](#model)
- [BrandSchema (interface)](#brandschema-interface)
- [Schema (interface)](#schema-interface)
- [parsers](#parsers)
- [clamp](#clamp)
Expand Down Expand Up @@ -262,7 +263,7 @@ Schema<A> + B -> Schema<A & Brand<B>>
export declare const brand: <B extends string, A>(
brand: B,
options?: AnnotationOptions<A> | undefined
) => (self: Schema<A>) => Schema<any>
) => (self: Schema<A>) => BrandSchema<any>
```
**Example**
Expand Down Expand Up @@ -481,8 +482,8 @@ using the provided decoding functions.
```ts
export declare const transformOrFail: <A, B>(
to: Schema<B>,
decode: (input: A, options?: AST.ParseOptions | undefined) => Either<readonly [ParseError, ...ParseError[]], B>,
encode: (input: B, options?: AST.ParseOptions | undefined) => Either<readonly [ParseError, ...ParseError[]], A>
decode: (input: A, options?: AST.ParseOptions | undefined) => E.Either<readonly [ParseError, ...ParseError[]], B>,
encode: (input: B, options?: AST.ParseOptions | undefined) => E.Either<readonly [ParseError, ...ParseError[]], A>
) => (self: Schema<A>) => Schema<B>
```

Expand Down Expand Up @@ -896,6 +897,16 @@ Added in v1.0.0

# model

## BrandSchema (interface)

**Signature**

```ts
export interface BrandSchema<A extends Brand<any>> extends Schema<A>, Brand.Constructor<A> {}
```

Added in v1.0.0

## Schema (interface)

**Signature**
Expand Down
13 changes: 13 additions & 0 deletions docs/modules/index.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Added in v1.0.0
- [encodeOrThrow](#encodeorthrow)
- [failure](#failure)
- [failures](#failures)
- [getOption](#getoption)
- [is](#is)
- [isFailure](#isfailure)
- [isSuccess](#issuccess)
Expand Down Expand Up @@ -118,6 +119,18 @@ export declare const failures: (
Added in v1.0.0
## getOption
**Signature**
```ts
export declare const getOption: <A>(
schema: Schema<A>
) => (input: unknown, options?: ParseOptions | undefined) => Option<A>
```
Added in v1.0.0
## is
**Signature**
Expand Down
48 changes: 23 additions & 25 deletions src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,10 @@
* @since 1.0.0
*/

import { isBoolean } from "@effect/data/Boolean"
import { pipe } from "@effect/data/Function"
import { isNumber } from "@effect/data/Number"
import * as O from "@effect/data/Option"
import {
isBigint,
isNever,
isNotNullable,
isObject,
isRecord,
isString,
isSymbol,
isUndefined
} from "@effect/data/Predicate"
import type { Option } from "@effect/data/Option"
import * as P from "@effect/data/Predicate"
import * as RA from "@effect/data/ReadonlyArray"
import * as H from "@effect/schema/annotation/Hook"
import * as AST from "@effect/schema/AST"
Expand Down Expand Up @@ -48,6 +38,14 @@ export const decode = <A>(
schema: Schema<A>
): (input: unknown, options?: ParseOptions) => ParseResult<A> => parserFor(schema).parse

/**
* @category decoding
* @since 1.0.0
*/
export const getOption = <A>(schema: Schema<A>) =>
(input: unknown, options?: ParseOptions): Option<A> =>
O.fromEither(parserFor(schema).parse(input, options))

/**
* @category decoding
* @since 1.0.0
Expand Down Expand Up @@ -139,34 +137,34 @@ const parserFor = <A>(
(u): u is typeof ast.symbol => u === ast.symbol
)
case "UndefinedKeyword":
return I.fromRefinement(I.makeSchema(ast), isUndefined)
return I.fromRefinement(I.makeSchema(ast), P.isUndefined)
case "VoidKeyword":
return I.fromRefinement(I.makeSchema(ast), isUndefined)
return I.fromRefinement(I.makeSchema(ast), P.isUndefined)
case "NeverKeyword":
return I.fromRefinement(I.makeSchema(ast), isNever)
return I.fromRefinement(I.makeSchema(ast), P.isNever)
case "UnknownKeyword":
case "AnyKeyword":
return make(I.makeSchema(ast), PR.success)
case "StringKeyword":
return I.fromRefinement(I.makeSchema(ast), isString)
return I.fromRefinement(I.makeSchema(ast), P.isString)
case "NumberKeyword":
return I.fromRefinement(I.makeSchema(ast), isNumber)
return I.fromRefinement(I.makeSchema(ast), P.isNumber)
case "BooleanKeyword":
return I.fromRefinement(I.makeSchema(ast), isBoolean)
return I.fromRefinement(I.makeSchema(ast), P.isBoolean)
case "BigIntKeyword":
return I.fromRefinement(I.makeSchema(ast), isBigint)
return I.fromRefinement(I.makeSchema(ast), P.isBigint)
case "SymbolKeyword":
return I.fromRefinement(I.makeSchema(ast), isSymbol)
return I.fromRefinement(I.makeSchema(ast), P.isSymbol)
case "ObjectKeyword":
return I.fromRefinement(I.makeSchema(ast), isObject)
return I.fromRefinement(I.makeSchema(ast), P.isObject)
case "Enums":
return I.fromRefinement(
I.makeSchema(ast),
(u): u is any => ast.enums.some(([_, value]) => value === u)
)
case "TemplateLiteral": {
const regex = I.getTemplateLiteralRegex(ast)
return I.fromRefinement(I.makeSchema(ast), (u): u is any => isString(u) && regex.test(u))
return I.fromRefinement(I.makeSchema(ast), (u): u is any => P.isString(u) && regex.test(u))
}
case "Tuple": {
const elements = ast.elements.map((e) => go(e.type))
Expand Down Expand Up @@ -285,7 +283,7 @@ const parserFor = <A>(
}
case "TypeLiteral": {
if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
return I.fromRefinement(I.makeSchema(ast), isNotNullable)
return I.fromRefinement(I.makeSchema(ast), P.isNotNullable)
}
const propertySignaturesTypes = ast.propertySignatures.map((f) => go(f.type))
const indexSignatures = ast.indexSignatures.map((is) =>
Expand All @@ -294,7 +292,7 @@ const parserFor = <A>(
return make(
I.makeSchema(ast),
(input: unknown, options) => {
if (!isRecord(input)) {
if (!P.isRecord(input)) {
return PR.failure(PR.type(unknownRecord, input))
}
const output: any = {}
Expand Down Expand Up @@ -417,7 +415,7 @@ const parserFor = <A>(

if (len > 0) {
// if there is at least one key then input must be an object
if (isRecord(input)) {
if (P.isRecord(input)) {
for (let i = 0; i < len; i++) {
const name = ownKeys[i]
const buckets = searchTree.keys[name].buckets
Expand Down
40 changes: 36 additions & 4 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/

import type { Brand } from "@effect/data/Brand"
import { RefinedConstructorsTypeId } from "@effect/data/Brand"
import * as E from "@effect/data/Either"
import { pipe } from "@effect/data/Function"
import type { Option } from "@effect/data/Option"
import type { Predicate, Refinement } from "@effect/data/Predicate"
Expand All @@ -12,11 +14,13 @@ import * as AST from "@effect/schema/AST"
import type { ParseOptions } from "@effect/schema/AST"
import * as DataDate from "@effect/schema/data/Date"
import * as N from "@effect/schema/data/Number"
import * as O from "@effect/schema/data/Object"
import * as DataObject from "@effect/schema/data/Object"
import * as DataOption from "@effect/schema/data/Option"
import * as SRA from "@effect/schema/data/ReadonlyArray"
import * as S from "@effect/schema/data/String"
import { formatErrors } from "@effect/schema/formatter/Tree"
import * as I from "@effect/schema/internal/common"
import * as P from "@effect/schema/Parser"
import type { ParseResult } from "@effect/schema/ParseResult"

/**
Expand Down Expand Up @@ -82,7 +86,7 @@ export const enums = <A extends { [x: string]: string | number }>(
export const instanceOf: <A extends abstract new(...args: any) => any>(
constructor: A,
annotationOptions?: AnnotationOptions<object>
) => Schema<InstanceType<A>> = O.instanceOf
) => Schema<InstanceType<A>> = DataObject.instanceOf

/**
* @since 1.0.0
Expand Down Expand Up @@ -563,6 +567,12 @@ export const getPropertySignatures = <A>(schema: Schema<A>): { [K in keyof A]: S
return out as any
}

/**
* @category model
* @since 1.0.0
*/
export interface BrandSchema<A extends Brand<any>> extends Schema<A>, Brand.Constructor<A> {}

/**
* Returns a nominal branded schema by applying a brand to a given schema.
*
Expand All @@ -583,10 +593,32 @@ export const getPropertySignatures = <A>(schema: Schema<A>): { [K in keyof A]: S
* @category combinators
* @since 1.0.0
*/
export const brand: <B extends string, A>(
export const brand = <B extends string, A>(
brand: B,
options?: AnnotationOptions<A>
) => (self: Schema<A>) => Schema<A & Brand<B>> = I.brand
) =>
(self: Schema<A>): BrandSchema<A & Brand<B>> => {
const annotations = I.toAnnotations(options)
annotations[A.BrandId] = [...getBrands(self.ast), brand]
const ast = AST.mergeAnnotations(self.ast, annotations)
const schema: Schema<A & Brand<B>> = make(ast)
const decodeOrThrow = P.decodeOrThrow(schema)
const getOption = P.getOption(schema)
const decode = P.decode(schema)
const is = P.is(schema)
const out: any = Object.assign((input: unknown) => decodeOrThrow(input), {
[RefinedConstructorsTypeId]: RefinedConstructorsTypeId,
ast,
option: (input: unknown) => getOption(input),
either: (input: unknown) =>
E.mapLeft(decode(input), (errors) => [{ meta: input, message: formatErrors(errors) }]),
refine: (input: unknown): input is A & Brand<B> => is(input)
})
return out
}

const getBrands = (ast: AST.AST): Array<string> =>
(ast.annotations[A.BrandId] as Array<string> | undefined) || []

/**
* @category combinators
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export {
* @since 1.0.0
*/
encodeOrThrow,
/**
* @since 1.0.0
*/
getOption,
/**
* @since 1.0.0
*/
Expand Down
18 changes: 4 additions & 14 deletions src/internal/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @since 1.0.0
*/

import type { Brand } from "@effect/data/Brand"
import * as E from "@effect/data/Either"
import { pipe } from "@effect/data/Function"
import * as O from "@effect/data/Option"
Expand Down Expand Up @@ -84,7 +83,10 @@ export const typeAlias = (
export const annotations = (annotations: AST.Annotated["annotations"]) =>
<A>(self: S.Schema<A>): S.Schema<A> => makeSchema(AST.mergeAnnotations(self.ast, annotations))

const toAnnotations = <A>(options?: S.AnnotationOptions<A>): AST.Annotated["annotations"] => {
/** @internal */
export const toAnnotations = <A>(
options?: S.AnnotationOptions<A>
): AST.Annotated["annotations"] => {
const annotations: AST.Annotated["annotations"] = {}
if (options?.typeId !== undefined) {
const typeId = options?.typeId
Expand Down Expand Up @@ -135,18 +137,6 @@ export function filter<A>(
return (from) => makeSchema(AST.createRefinement(from.ast, predicate, toAnnotations(options)))
}

const getBrands = (ast: AST.AST): Array<string> =>
(ast.annotations[A.BrandId] as Array<string> | undefined) || []

/** @internal */
export const brand = <B extends string, A>(brand: B, options?: S.AnnotationOptions<A>) =>
(self: S.Schema<A>): S.Schema<A & Brand<B>> => {
const annotations = toAnnotations(options)
annotations[A.BrandId] = [...getBrands(self.ast), brand]
const ast = AST.mergeAnnotations(self.ast, annotations)
return makeSchema(ast)
}

/** @internal */
export const transformOrFail = <A, B>(
to: S.Schema<B>,
Expand Down
Loading

0 comments on commit c935ff2

Please sign in to comment.