From a5950d14e5868aa88e1c263d14e305185debbc30 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Thu, 14 Dec 2023 14:12:04 +0100 Subject: [PATCH] add Secret (#654) Co-authored-by: Sebastian Lorenz --- .changeset/unlucky-donuts-admire.md | 5 +++ README.md | 17 +++++++++ docs/modules/Schema.ts.md | 43 +++++++++++++++++++++++ dtslint/Schema.ts | 13 +++++++ src/Schema.ts | 53 +++++++++++++++++++++++++++++ test/Secret/Secret.test.ts | 24 +++++++++++++ test/Secret/SecretFromSelf.test.ts | 34 ++++++++++++++++++ 7 files changed, 189 insertions(+) create mode 100644 .changeset/unlucky-donuts-admire.md create mode 100644 test/Secret/Secret.test.ts create mode 100644 test/Secret/SecretFromSelf.test.ts diff --git a/.changeset/unlucky-donuts-admire.md b/.changeset/unlucky-donuts-admire.md new file mode 100644 index 000000000..d73ad7c84 --- /dev/null +++ b/.changeset/unlucky-donuts-admire.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": patch +--- + +added S.Secret diff --git a/README.md b/README.md index 0c8bf1206..1d6ba61c7 100644 --- a/README.md +++ b/README.md @@ -2372,6 +2372,23 @@ parse(Duration.decode("6 seconds")); // 6 seconds parse(Duration.decode("11 seconds")); // 10 seconds ``` +### Secret transformations + +#### Secret + +Converts a `string` into a `Secret`. + +```ts +import * as S from "@effect/schema/Schema"; +import * as Secret from "effect/Secret"; + +// $ExpectType Schema +const schema = S.Secret; + +const parse = S.parseSync(schema); +parse("keep it secret, keep it safe"); // Secret +``` + ### Symbol transformations #### symbol diff --git a/docs/modules/Schema.ts.md b/docs/modules/Schema.ts.md index 89c65c2d8..15c9a8a70 100644 --- a/docs/modules/Schema.ts.md +++ b/docs/modules/Schema.ts.md @@ -92,6 +92,11 @@ Added in v1.0.0 - [ReadonlySet transformations](#readonlyset-transformations) - [readonlySet](#readonlyset) - [readonlySetFromSelf](#readonlysetfromself) +- [Secret constructors](#secret-constructors) + - [Secret](#secret) + - [SecretFromSelf](#secretfromself) +- [Secret transformations](#secret-transformations) + - [secret](#secret-1) - [Uint8Array constructors](#uint8array-constructors) - [Uint8Array](#uint8array) - [Uint8ArrayFromSelf](#uint8arrayfromself) @@ -1230,6 +1235,44 @@ export declare const readonlySetFromSelf: (item: Schema) => Schema +``` + +Added in v1.0.0 + +## SecretFromSelf + +**Signature** + +```ts +export declare const SecretFromSelf: Schema +``` + +Added in v1.0.0 + +# Secret transformations + +## secret + +A combinator that transforms a `string` into a `Secret`. + +**Signature** + +```ts +export declare const secret: (self: Schema) => Schema +``` + +Added in v1.0.0 + # Uint8Array constructors ## Uint8Array diff --git a/dtslint/Schema.ts b/dtslint/Schema.ts index f52081e1f..f9d4addfc 100644 --- a/dtslint/Schema.ts +++ b/dtslint/Schema.ts @@ -792,3 +792,16 @@ S.durationFromNanos(S.bigintFromSelf) // $ExpectType Schema S.DurationFromNanos + +// --------------------------------------------- +// Secret +// --------------------------------------------- + +// $ExpectType Schema +S.Secret + +// $ExpectType Schema +S.SecretFromSelf + +// $ExpectType Schema +S.secret(S.string) diff --git a/src/Schema.ts b/src/Schema.ts index bdfd7ad3a..caa53717f 100644 --- a/src/Schema.ts +++ b/src/Schema.ts @@ -25,6 +25,7 @@ import { pipeArguments } from "effect/Pipeable" import * as Predicate from "effect/Predicate" import * as ReadonlyArray from "effect/ReadonlyArray" import * as Request from "effect/Request" +import * as Secret from "effect/Secret" import * as S from "effect/String" import type { Equals, Simplify } from "effect/Types" import type { Arbitrary } from "./Arbitrary.js" @@ -2993,6 +2994,58 @@ export const NonNegativeBigint: Schema = bigint.pipe( */ export const BigintFromNumber: Schema = bigintFromNumber(number) +// --------------------------------------------- +// Secret +// --------------------------------------------- + +/** + * @category Secret constructors + * @since 1.0.0 + */ +export const SecretFromSelf: Schema = declare( + [], + struct({}), + () => (u, _, ast) => + Secret.isSecret(u) ? + ParseResult.succeed(u) + : ParseResult.fail(ParseResult.type(ast, u)), + { + [AST.IdentifierAnnotationId]: "Secret", + [hooks.PrettyHookId]: (): Pretty.Pretty => (secret) => String(secret), + [hooks.ArbitraryHookId]: (): Arbitrary => (fc) => + fc.string().map((_) => Secret.fromString(_)) + } +) + +/** + * A combinator that transforms a `string` into a `Secret`. + * + * @category Secret transformations + * @since 1.0.0 + */ +export const secret = ( + self: Schema +): Schema => + transform( + self, + SecretFromSelf, + (str) => Secret.fromString(str), + (secret) => Secret.value(secret), + { strict: false } + ) + +const _Secret: Schema = secret(string) + +export { + /** + * A schema that transforms a `string` into a `Secret`. + * + * @category Secret constructors + * @since 1.0.0 + */ + _Secret as Secret +} + // --------------------------------------------- // Duration constructors // --------------------------------------------- diff --git a/test/Secret/Secret.test.ts b/test/Secret/Secret.test.ts new file mode 100644 index 000000000..63c4f2569 --- /dev/null +++ b/test/Secret/Secret.test.ts @@ -0,0 +1,24 @@ +import * as S from "@effect/schema/Schema" +import * as Util from "@effect/schema/test/util" +import { Secret } from "effect" +import { describe, it } from "vitest" + +describe("Schema/Secret", () => { + const schema = S.Secret + + it("property tests", () => { + Util.roundtrip(schema) + }) + + it("decoding", () => { + Util.expectParseSuccess(schema, "keep me safe", Secret.fromString("keep me safe")) + }) + + it("encoding", () => { + Util.expectEncodeSuccess( + schema, + Secret.fromString("keep me safe"), + "keep me safe" + ) + }) +}) diff --git a/test/Secret/SecretFromSelf.test.ts b/test/Secret/SecretFromSelf.test.ts new file mode 100644 index 000000000..f7c59ea2b --- /dev/null +++ b/test/Secret/SecretFromSelf.test.ts @@ -0,0 +1,34 @@ +import * as Pretty from "@effect/schema/Pretty" +import * as S from "@effect/schema/Schema" +import * as Util from "@effect/schema/test/util" +import { Secret } from "effect" +import { describe, expect, it } from "vitest" + +describe("Schema/SecretFromSelf", () => { + const schema = S.SecretFromSelf + + it("property tests", () => { + Util.roundtrip(schema) + }) + + it("decoding", () => { + Util.expectParseSuccess( + schema, + Secret.fromString("keep me safe"), + Secret.fromString("keep me safe") + ) + }) + + it("encoding", () => { + Util.expectEncodeSuccess( + schema, + Secret.fromString("keep me safe"), + Secret.fromString("keep me safe") + ) + }) + + it("Pretty", () => { + const pretty = Pretty.to(schema) + expect(pretty(Secret.fromString("keep me safe"))).toEqual(`Secret()`) + }) +})