Skip to content

Commit

Permalink
feat: add types for deliveryFunction implementation (#316)
Browse files Browse the repository at this point in the history
  • Loading branch information
BasKiers authored and andipaetzold committed Aug 18, 2023
1 parent 006f82f commit b807a25
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 124 deletions.
124 changes: 0 additions & 124 deletions src/requests/typings.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/requests/typings/appAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PlainClientAPI } from 'contentful-management'

export type AppActionCallContext = {
cma: PlainClientAPI
appActionCallContext: {
spaceId: string
environmentId: string
appInstallationId: string
userId: string
}
}
62 changes: 62 additions & 0 deletions src/requests/typings/deliveryFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Remove when this eslint rule covers all the cases
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md
/*eslint-disable no-unused-vars*/

export enum DeliveryFunctionRequestEventType {
GRAPHQL_FIELD_MAPPING = 'graphql.field.mapping',
GRAPHQL_QUERY = 'graphql.query',
}

export type GraphQLFieldTypeMappingRequest = {
fields: { contentTypeId: string; field: Field }[]
}

type Field = {
id: string
type: string
}

export type GraphQLFieldTypeMappingResponse = GraphQLFieldTypeMapping[]

export type GraphQLFieldTypeMapping = {
contentTypeId: string
fieldId: string
graphQLOutputType: string
graphQLQueryField: string
graphQLQueryArgument: string
}

export type GraphQLQueryRequest = {
query: string
variables: Record<string, unknown>
operationName?: string
}

/**
* @see https://spec.graphql.org/October2021/#sec-Response
*/
export type GraphQLQueryResponse = {
data?: Record<string, any> | null
errors?: readonly Record<string, any>[]
extensions?: Record<string, unknown>
}

/**
* P: Possibility to type app installation parameters
*/
export type DeliveryFunctionEventContext<P extends Record<string, any> = Record<string, any>> = {
spaceId: string
environmentId: string
appInstallationParameters: P
}

export type DeliveryFunctionEventHandlers = {
[DeliveryFunctionRequestEventType.GRAPHQL_FIELD_MAPPING]: (
event: GraphQLFieldTypeMappingRequest,
context: DeliveryFunctionEventContext
) => Promise<GraphQLFieldTypeMappingResponse>
[DeliveryFunctionRequestEventType.GRAPHQL_QUERY]: (
event: GraphQLQueryRequest,
context: DeliveryFunctionEventContext
) => Promise<GraphQLQueryResponse>
}
4 changes: 4 additions & 0 deletions src/requests/typings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './appAction'
export * from './deliveryFunction'
export * from './request'
export * from './validators'
55 changes: 55 additions & 0 deletions src/requests/typings/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Remove when this eslint rule covers all the cases
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md
/*eslint-disable no-unused-vars*/
import { CanonicalRequest } from './validators'

export enum ContentfulHeader {
Timestamp = 'x-contentful-timestamp',
SignedHeaders = 'x-contentful-signed-headers',
Signature = 'x-contentful-signature',
}

export enum ContentfulContextHeader {
SpaceId = 'x-contentful-space-id',
EnvironmentId = 'x-contentful-environment-id',
UserId = 'x-contentful-user-id',
AppId = 'x-contentful-app-id',
}

export type NormalizedCanonicalRequest = {
method: CanonicalRequest['method']
path: CanonicalRequest['path']
headers: [key: string, value: string][]
body: CanonicalRequest['body']
}

export type SubjectHeadersApp = { appId: string }
export type AppContextSignedHeaders = { [ContentfulContextHeader.AppId]: string }
export type SubjectHeadersUser = { userId: string }
export type UserContextSignedHeaders = { [ContentfulContextHeader.UserId]: string }

export type Context<SubjectContext> = {
spaceId: string
envId: string
} & SubjectContext

type SignedHeadersWithoutSubject = {
[ContentfulContextHeader.SpaceId]: string
[ContentfulContextHeader.EnvironmentId]: string
}

export type SignedContextHeaders<SubjectSignedHeaders> = SignedHeadersWithoutSubject &
SubjectSignedHeaders

export type SignedRequestWithoutContextHeaders = {
[key in ContentfulHeader]: string
}
export type SignedRequestWithContextHeadersWithUser = SignedRequestWithoutContextHeaders &
SignedContextHeaders<UserContextSignedHeaders>
export type SignedRequestWithContextHeadersWithApp = SignedRequestWithoutContextHeaders &
SignedContextHeaders<AppContextSignedHeaders>

export type SignedRequestHeaders =
| SignedRequestWithContextHeadersWithUser
| SignedRequestWithContextHeadersWithApp
| SignedRequestWithoutContextHeaders
59 changes: 59 additions & 0 deletions src/requests/typings/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as runtypes from 'runtypes'

const MethodValidator = runtypes.Union(
runtypes.Literal('GET'),
runtypes.Literal('PATCH'),
runtypes.Literal('HEAD'),
runtypes.Literal('POST'),
runtypes.Literal('DELETE'),
runtypes.Literal('OPTIONS'),
runtypes.Literal('PUT')
)

const PathValidator = runtypes.String.withConstraint((s) => s.startsWith('/'), {
name: 'CanonicalURI',
})

const SignatureValidator = runtypes.String.withConstraint((s) => s.length === 64, {
name: 'SignatureLength',
})

export const CanonicalRequestValidator = runtypes
.Record({
method: MethodValidator,
path: PathValidator,
})
.And(
runtypes.Partial({
headers: runtypes.Dictionary(runtypes.String, 'string'),
body: runtypes.String,
})
)
export type CanonicalRequest = runtypes.Static<typeof CanonicalRequestValidator>

export const SecretValidator = runtypes.String.withConstraint((s) => s.length === 64, {
name: 'SecretLength',
})
export type Secret = runtypes.Static<typeof SecretValidator>

// Only dates after 01-01-2020
export const TimestampValidator = runtypes.Number.withConstraint((n) => n > 1577836800000, {
name: 'TimestampAge',
})
export type Timestamp = runtypes.Static<typeof TimestampValidator>

const SignedHeadersValidator = runtypes
.Array(runtypes.String)
.withConstraint((l) => l.length >= 2, { name: 'MissingTimestampOrSignedHeaders' })

export const RequestMetadataValidator = runtypes.Record({
signature: SignatureValidator,
timestamp: TimestampValidator,
signedHeaders: SignedHeadersValidator,
})
export type RequestMetadata = runtypes.Static<typeof RequestMetadataValidator>

export const TimeToLiveValidator = runtypes.Number.withConstraint((n) => n >= 0, {
name: 'PositiveNumber',
})
export type TimeToLive = runtypes.Static<typeof TimeToLiveValidator>

0 comments on commit b807a25

Please sign in to comment.