diff --git a/.changeset/fresh-otters-tickle.md b/.changeset/fresh-otters-tickle.md new file mode 100644 index 00000000000..7d24416e424 --- /dev/null +++ b/.changeset/fresh-otters-tickle.md @@ -0,0 +1,5 @@ +--- +"@smithy/types": minor +--- + +add types for schemas diff --git a/packages/types/src/command.ts b/packages/types/src/command.ts index 101651f1ed7..fa1d2553eac 100644 --- a/packages/types/src/command.ts +++ b/packages/types/src/command.ts @@ -1,5 +1,6 @@ import { Handler, MiddlewareStack } from "./middleware"; import { MetadataBearer } from "./response"; +import { OperationSchema } from "./schema/schema"; /** * @public @@ -13,6 +14,8 @@ export interface Command< > extends CommandIO { readonly input: InputType; readonly middlewareStack: MiddlewareStack; + readonly schema?: OperationSchema; + resolveMiddleware( stack: MiddlewareStack, configuration: ResolvedConfiguration, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c370335c2ab..b1dfb30b38f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -21,6 +21,8 @@ export * from "./pagination"; export * from "./profile"; export * from "./response"; export * from "./retry"; +export * from "./schema/schema"; +export * from "./schema/sentinels"; export * from "./serde"; export * from "./shapes"; export * from "./signature"; @@ -30,6 +32,7 @@ export * from "./streaming-payload/streaming-blob-payload-input-types"; export * from "./streaming-payload/streaming-blob-payload-output-types"; export * from "./transfer"; export * from "./transform/client-payload-blob-type-narrow"; +export * from "./transform/mutable"; export * from "./transform/no-undefined"; export * from "./transform/type-transform"; export * from "./uri"; diff --git a/packages/types/src/schema/schema.ts b/packages/types/src/schema/schema.ts new file mode 100644 index 00000000000..86be66ad52a --- /dev/null +++ b/packages/types/src/schema/schema.ts @@ -0,0 +1,345 @@ +import type { EndpointV2 } from "../endpoint"; +import type { HandlerExecutionContext } from "../middleware"; +import type { MetadataBearer } from "../response"; +import type { EndpointBearer, SerdeFunctions } from "../serde"; +import type { + BigDecimalSchema, + BigIntegerSchema, + BlobSchema, + BooleanSchema, + DocumentSchema, + NumericSchema, + StreamingBlobSchema, + StringSchema, + TimestampDateTimeSchema, + TimestampDefaultSchema, + TimestampEpochSecondsSchema, + TimestampHttpDateSchema, +} from "./sentinels"; +import type { TraitBitVector } from "./traits"; + +/** + * A schema is an object or value that describes how to serialize/deserialize data. + * @public + */ +export type Schema = + | UnitSchema + | TraitsSchema + | SimpleSchema + | ListSchema + | MapSchema + | StructureSchema + | MemberSchema + | OperationSchema + | NormalizedSchema; + +/** + * Traits attached to schema objects. + * + * When this is a number, it refers to a pre-allocated + * trait combination that is equivalent to one of the + * object type's variations. + * + * @public + */ +export type SchemaTraits = TraitBitVector | SchemaTraitsObject; + +/** + * A schema that has traits. + * + * @public + */ +export interface TraitsSchema { + name: string; + traits: SchemaTraits; +} + +/** + * Simple schemas are those corresponding to simple Smithy types. + * @see https://smithy.io/2.0/spec/simple-types.html + * @public + */ +export type SimpleSchema = + | BlobSchemas + | StringSchema + | BooleanSchema + | NumericSchema + | BigIntegerSchema + | BigDecimalSchema + | DocumentSchema + | TimestampSchemas + | number; + +/** + * Sentinel value for Timestamp schema. + * "Default" means unspecified and to use the protocol serializer's default format. + * + * @public + */ +export type TimestampSchemas = + | TimestampDefaultSchema + | TimestampDateTimeSchema + | TimestampHttpDateSchema + | TimestampEpochSecondsSchema; + +/** + * Sentinel values for Blob schema. + * @public + */ +export type BlobSchemas = BlobSchema | StreamingBlobSchema; + +/** + * Signal value for the Smithy void value. Typically used for + * operation input and outputs. + * + * @internal + */ +export type UnitSchema = "unit"; + +/** + * See https://smithy.io/2.0/trait-index.html for individual definitions. + * + * @public + */ +export type SchemaTraitsObject = { + idempotent?: 1; + idempotencyToken?: 1; + sensitive?: 1; + sparse?: 1; + + /** + * timestampFormat is expressed by the schema sentinel values of 4, 5, 6, and 7, + * and not contained in trait objects. + * @deprecated use schema value. + */ + timestampFormat?: never; + + httpLabel?: 1; + httpHeader?: string; + httpQuery?: string; + httpPrefixHeaders?: string; + httpQueryParams?: 1; + httpPayload?: 1; + /** + * [method, path, statusCode] + */ + http?: [string, string, number]; + httpResponseCode?: 1; + /** + * [hostPrefix] + */ + endpoint?: [string]; + + xmlAttribute?: 1; + xmlName?: string; + /** + * [prefix, uri] + */ + xmlNamespace?: [string, string]; + xmlFlattened?: 1; + jsonName?: string; + + mediaType?: string; + error?: "client" | "server"; + + [traitName: string]: unknown; +}; + +/** + * Schema for the structure aggregate type. + * @public + */ +export interface StructureSchema extends TraitsSchema { + name: string; + traits: SchemaTraits; + members: Record; +} + +/** + * Schema for the list aggregate type. + * @public + */ +export interface ListSchema extends TraitsSchema { + name: string; + traits: SchemaTraits; + valueSchema: SchemaRef; +} + +/** + * Schema for the map aggregate type. + * @public + */ +export interface MapSchema extends TraitsSchema { + name: string; + traits: SchemaTraits; + keySchema: SchemaRef; + valueSchema: SchemaRef; +} + +/** + * @public + */ +export type MemberSchema = [SchemaRef, SchemaTraits]; + +/** + * Schema for an operation. + * + * @public + */ +export interface OperationSchema extends TraitsSchema { + name: string; + traits: SchemaTraits; + input: SchemaRef; + output: SchemaRef; +} + +/** + * Normalization wrapper for various schema data objects. + * @internal + */ +export interface NormalizedSchema extends TraitsSchema { + name: string; + traits: SchemaTraits; + getSchema(): Schema; + getName(): string | undefined; + isMemberSchema(): boolean; + isListSchema(): boolean; + isMapSchema(): boolean; + isStructSchema(): boolean; + isBlobSchema(): boolean; + isTimestampSchema(): boolean; + isStringSchema(): boolean; + isBooleanSchema(): boolean; + isNumericSchema(): boolean; + isBigIntegerSchema(): boolean; + isBigDecimalSchema(): boolean; + isStreaming(): boolean; + getMergedTraits(): SchemaTraitsObject; + getMemberTraits(): SchemaTraitsObject; + getOwnTraits(): SchemaTraitsObject; + /** + * For list/set/map. + */ + getValueSchema(): NormalizedSchema; + /** + * For struct/union. + */ + getMemberSchema(member: string): NormalizedSchema | undefined; + getMemberSchemas(): Record; +} + +/** + * A schema "reference" is either a schema or a function that + * provides a schema. This is useful for lazy loading, and to allow + * code generation to define schema out of dependency order. + * @public + */ +export type SchemaRef = Schema | (() => Schema); + +/** + * A codec creates serializers and deserializers for some format such as JSON, XML, or CBOR. + * + * @public + */ +export interface Codec extends ConfigurableSerdeContext { + createSerializer(): ShapeSerializer; + createDeserializer(): ShapeDeserializer; +} + +/** + * Configuration for codecs. Different protocols may share codecs, but require different behaviors from them. + * + * @public + */ +export type CodecSettings = { + timestampFormat: { + /** + * Whether to use member timestamp format traits. + */ + useTrait: boolean; + /** + * Default timestamp format. + */ + default: TimestampDateTimeSchema | TimestampHttpDateSchema | TimestampEpochSecondsSchema; + }; + /** + * Whether to use HTTP binding traits. + */ + httpBindings?: boolean; +}; + +/** + * Turns a serialization into a data object. + * @public + */ +export interface ShapeDeserializer extends ConfigurableSerdeContext { + /** + * Optionally async. + */ + read(schema: Schema, data: SerializationType): any | Promise; +} + +/** + * Turns a data object into a serialization. + * @public + */ +export interface ShapeSerializer extends ConfigurableSerdeContext { + write(schema: Schema, value: unknown): void; + + flush(): SerializationType; +} + +/** + * A client protocol defines how to convert a message (e.g. HTTP request/response) to and from a data object. + * @public + */ +export interface ClientProtocol extends ConfigurableSerdeContext { + /** + * @returns the Smithy qualified shape id. + */ + getShapeId(): string; + + getRequestType(): { new (...args: any[]): Request }; + getResponseType(): { new (...args: any[]): Response }; + + /** + * @returns the payload codec if the requests/responses have a symmetric format. + * It otherwise may return null. + */ + getPayloadCodec(): Codec; + + serializeRequest( + operationSchema: OperationSchema, + input: Input, + context: HandlerExecutionContext & SerdeFunctions & EndpointBearer + ): Promise; + + updateServiceEndpoint(request: Request, endpoint: EndpointV2): Request; + + deserializeResponse( + operationSchema: OperationSchema, + context: HandlerExecutionContext & SerdeFunctions, + response: Response + ): Promise; +} + +/** + * Allows a protocol, codec, or serde utility to accept the serdeContext + * from a client configuration or request/response handlerExecutionContext. + * + * @public + */ +export interface ConfigurableSerdeContext { + setSerdeContext(serdeContext: SerdeFunctions): void; +} + +// TODO(schema): transports are not required in the current implementation. +// /** +// * @public +// */ +// export interface Transport { +// getRequestType(): { new (...args: any[]): Request }; +// getResponseType(): { new (...args: any[]): Response }; +// +// send(context: HandlerExecutionContext, request: Request): Promise; +// } diff --git a/packages/types/src/schema/sentinels.ts b/packages/types/src/schema/sentinels.ts new file mode 100644 index 00000000000..0000b9c2a29 --- /dev/null +++ b/packages/types/src/schema/sentinels.ts @@ -0,0 +1,79 @@ +// =============== Simple types =================== + +/** + * The blob Smithy type, in JS as Uint8Array and other representations + * such as Buffer, string, or Readable(Stream) depending on circumstances. + * @public + */ +export type BlobSchema = 0b0001_0101; // 21 + +/** + * @public + */ +export type StreamingBlobSchema = 0b0010_1010; // 42 + +/** + * @public + */ +export type BooleanSchema = 0b0000_0010; // 2 + +/** + * Includes string and enum Smithy types. + * @public + */ +export type StringSchema = 0b0000_0000; // 0 + +/** + * Includes all numeric Smithy types except bigInteger and bigDecimal. + * byte, short, integer, long, float, double, intEnum. + * + * @public + */ +export type NumericSchema = 0b0000_0001; // 1 + +/** + * @public + */ +export type BigIntegerSchema = 0b0001_0001; // 17 + +/** + * @public + */ +export type BigDecimalSchema = 0b0001_0011; // 19 + +/** + * @public + */ +export type DocumentSchema = 0b0000_1111; // 15 + +/** + * Smithy type timestamp, in JS as native Date object. + * @public + */ +export type TimestampDefaultSchema = 0b0000_0100; // 4 +/** + * @public + */ +export type TimestampDateTimeSchema = 0b0000_0101; // 5 +/** + * @public + */ +export type TimestampHttpDateSchema = 0b0000_0110; // 6 +/** + * @public + */ +export type TimestampEpochSecondsSchema = 0b0000_0111; // 7 + +// =============== Aggregate types =================== + +/** + * Additional bit indicating the type is a list. + * @public + */ +export type ListSchemaModifier = 0b0100_0000; // 64 + +/** + * Additional bit indicating the type is a map. + * @public + */ +export type MapSchemaModifier = 0b1000_0000; // 128 diff --git a/packages/types/src/schema/traits.ts b/packages/types/src/schema/traits.ts new file mode 100644 index 00000000000..979b90b4934 --- /dev/null +++ b/packages/types/src/schema/traits.ts @@ -0,0 +1,55 @@ +/** + * A bitvector representing a traits object. + * + * Vector index to trait: + * 0 - httpLabel + * 1 - idempotent + * 2 - idempotencyToken + * 3 - sensitive + * 4 - httpPayload + * 5 - httpResponseCode + * 6 - httpQueryParams + * + * The singular trait values are enumerated for quick identification, but + * combination values are left to the `number` union type. + * + * @public + */ +export type TraitBitVector = + | HttpLabelBitMask + | IdempotentBitMask + | IdempotencyTokenBitMask + | SensitiveBitMask + | HttpPayloadBitMask + | HttpResponseCodeBitMask + | HttpQueryParamsBitMask + | number; + +/** + * @public + */ +export type HttpLabelBitMask = 1; +/** + * @public + */ +export type IdempotentBitMask = 2; +/** + * @public + */ +export type IdempotencyTokenBitMask = 4; +/** + * @public + */ +export type SensitiveBitMask = 8; +/** + * @public + */ +export type HttpPayloadBitMask = 16; +/** + * @public + */ +export type HttpResponseCodeBitMask = 32; +/** + * @public + */ +export type HttpQueryParamsBitMask = 64; diff --git a/packages/types/src/serde.ts b/packages/types/src/serde.ts index 5af727e6aab..18822f64d07 100644 --- a/packages/types/src/serde.ts +++ b/packages/types/src/serde.ts @@ -1,4 +1,5 @@ import { Endpoint } from "./http"; +import { ClientProtocol } from "./schema/schema"; import { RequestHandler } from "./transfer"; import { Decoder, Encoder, Provider } from "./util"; @@ -34,6 +35,7 @@ export interface StreamCollector { export interface SerdeContext extends SerdeFunctions, EndpointBearer { requestHandler: RequestHandler; disableHostPrefix: boolean; + protocol?: ClientProtocol; } /** diff --git a/packages/types/src/transform/mutable.ts b/packages/types/src/transform/mutable.ts new file mode 100644 index 00000000000..5f7a47dee93 --- /dev/null +++ b/packages/types/src/transform/mutable.ts @@ -0,0 +1,6 @@ +/** + * @internal + */ +export type Mutable = { + -readonly [Property in keyof Type]: Type[Property]; +};