Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve callable and schema validator types #128

Merged
merged 11 commits into from
Sep 10, 2024
2 changes: 2 additions & 0 deletions functions/models/src/functions/checkInvitationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export const checkInvitationCodeInputSchema = z.object({
export type CheckInvitationCodeInput = z.input<
typeof checkInvitationCodeInputSchema
>

export type CheckInvitationCodeOutput = undefined
pauljohanneskraft marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 3 additions & 6 deletions functions/models/src/functions/createInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ export const createInvitationInputSchema = z.object({
})
export type CreateInvitationInput = z.input<typeof createInvitationInputSchema>

export const createInvitationOutputSchema = z.object({
id: z.string(),
})
export type CreateInvitationOutput = z.output<
typeof createInvitationOutputSchema
>
export interface CreateInvitationOutput {
pauljohanneskraft marked this conversation as resolved.
Show resolved Hide resolved
id: string
}
2 changes: 2 additions & 0 deletions functions/models/src/functions/deleteInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export const deleteInvitationInputSchema = z.object({
invitationCode: z.string(),
})
export type DeleteInvitationInput = z.input<typeof deleteInvitationInputSchema>

export type DeleteInvitationOutput = undefined
2 changes: 2 additions & 0 deletions functions/models/src/functions/deleteUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export const deleteUserInputSchema = z.object({
userId: z.string(),
})
export type DeleteUserInput = z.input<typeof deleteUserInputSchema>

export type DeleteUserOutput = undefined
2 changes: 2 additions & 0 deletions functions/models/src/functions/dismissMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export const dismissMessageInputSchema = z.object({
didPerformAction: optionalishDefault(z.boolean(), false),
})
export type DismissMessageInput = z.input<typeof dismissMessageInputSchema>

export type DismissMessageOutput = undefined
9 changes: 3 additions & 6 deletions functions/models/src/functions/exportHealthSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ export type ExportHealthSummaryInput = z.input<
typeof exportHealthSummaryInputSchema
>

export const exportHealthSummaryOutputSchema = z.object({
content: z.string(),
})
export type ExportHealthSummaryOutput = z.output<
typeof exportHealthSummaryOutputSchema
>
export interface ExportHealthSummaryOutput {
content: string
}
39 changes: 15 additions & 24 deletions functions/models/src/functions/getUsersInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
//

import { z } from 'zod'
import { optionalish, optionalishDefault } from '../helpers/optionalish.js'
import { userConverter } from '../types/user.js'
import { userAuthConverter } from '../types/userAuth.js'
import { optionalishDefault } from '../helpers/optionalish.js'
import { type InferEncoded } from '../helpers/schemaConverter'
import { type userConverter } from '../types/user.js'
import { type userAuthConverter } from '../types/userAuth.js'

export const getUsersInformationInputSchema = z.object({
includeUserData: optionalishDefault(z.boolean(), false),
Expand All @@ -19,26 +20,16 @@ export type GetUsersInformationInput = z.input<
typeof getUsersInformationInputSchema
>

export const userInformationSchema = z.object({
auth: z.lazy(() => userAuthConverter.value.schema),
user: optionalish(z.lazy(() => userConverter.value.schema)),
})
export type UserInformation = z.output<typeof userInformationSchema>
export interface UserInformation {
auth: InferEncoded<typeof userAuthConverter>
user: InferEncoded<typeof userConverter> | undefined
}

export const getUsersInformationOutputSchema = z.record(
z
.object({
data: userInformationSchema,
})
.or(
z.object({
error: z.object({
code: z.string(),
message: z.string(),
}),
}),
),
)
export type GetUsersInformationOutput = z.output<
typeof getUsersInformationOutputSchema
export type GetUsersInformationOutput = Record<
string,
| {
data: UserInformation
error?: undefined
}
| { data?: undefined; error: { code: string; message: string } }
>
2 changes: 2 additions & 0 deletions functions/models/src/functions/registerDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ import { userDeviceConverter } from '../types/userDevice.js'

export const registerDeviceInputSchema = userDeviceConverter.value.schema
export type RegisterDeviceInput = z.input<typeof registerDeviceInputSchema>

export type RegisterDeviceOutput = undefined
2 changes: 2 additions & 0 deletions functions/models/src/functions/unregisterDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export const unregisterDeviceInputSchema = z.object({
platform: z.nativeEnum(UserDevicePlatform),
})
export type UnregisterDeviceInput = z.input<typeof unregisterDeviceInputSchema>

export type UnregisterDeviceOutput = undefined
2 changes: 2 additions & 0 deletions functions/models/src/functions/updateUserInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export const updateUserInformationInputSchema = z.object({
export type UpdateUserInformationInput = z.input<
typeof updateUserInformationInputSchema
>

export type UpdateUserInformationOutput = undefined
6 changes: 2 additions & 4 deletions functions/models/src/helpers/optionalish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@

import { z } from 'zod'

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export function optionalish<T extends z.ZodType<any, any, any>>(type: T) {
export function optionalish<T extends z.Schema>(type: T) {
return type.or(z.null().transform(() => undefined)).optional()
}

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export function optionalishDefault<T extends z.ZodType<any, any, any>>(
export function optionalishDefault<T extends z.Schema>(
type: T,
defaultValue: z.output<T>,
) {
Expand Down
16 changes: 12 additions & 4 deletions functions/models/src/helpers/schemaConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
//

import { type z } from 'zod'
import { type Lazy } from './lazy'

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export class SchemaConverter<Schema extends z.ZodType<any, any, any>> {
export class SchemaConverter<Schema extends z.Schema, Encoded> {
// Properties

readonly schema: Schema
readonly encode: (value: z.output<Schema>) => z.input<Schema>
readonly encode: (value: z.output<Schema>) => Encoded

get value(): this {
return this
Expand All @@ -23,9 +23,17 @@ export class SchemaConverter<Schema extends z.ZodType<any, any, any>> {

constructor(input: {
schema: Schema
encode: (value: z.output<Schema>) => z.input<Schema>
encode: (value: z.output<Schema>) => Encoded
}) {
this.schema = input.schema
this.encode = input.encode
}
}

/* eslint-disable @typescript-eslint/no-explicit-any */
export type InferEncoded<Input> =
pauljohanneskraft marked this conversation as resolved.
Show resolved Hide resolved
Input extends SchemaConverter<any, any> ? ReturnType<Input['encode']>
: Input extends Lazy<SchemaConverter<any, any>> ?
ReturnType<Input['value']['encode']>
: never
/* eslint-enable @typescript-eslint/no-explicit-any */
7 changes: 5 additions & 2 deletions functions/src/functions/checkInvitationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
// Based on:
// https://github.com/StanfordBDHG/PediatricAppleWatchStudy/pull/54/files

import { checkInvitationCodeInputSchema } from '@stanfordbdhg/engagehf-models'
import {
checkInvitationCodeInputSchema,
type CheckInvitationCodeOutput,
} from '@stanfordbdhg/engagehf-models'
import { https, logger } from 'firebase-functions/v2'
import { validatedOnCall } from './helpers.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const checkInvitationCode = validatedOnCall(
'checkInvitationCode',
checkInvitationCodeInputSchema,
async (request): Promise<void> => {
async (request): Promise<CheckInvitationCodeOutput> => {
const factory = getServiceFactory()
const userId = factory.credential(request.auth).userId

Expand Down
6 changes: 3 additions & 3 deletions functions/src/functions/createInvitation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { expectError } from '../tests/helpers.js'

describeWithEmulators('function: createInvitation', (env) => {
it('should create an invitation for a clinician', async () => {
const input: z.output<typeof createInvitationInputSchema> = {
const input: z.input<typeof createInvitationInputSchema> = {
auth: {
displayName: 'Test User',
email: '[email protected]',
Expand Down Expand Up @@ -48,7 +48,7 @@ describeWithEmulators('function: createInvitation', (env) => {
})

it('should create an invitation for a patient', async () => {
const input: z.output<typeof createInvitationInputSchema> = {
const input: z.input<typeof createInvitationInputSchema> = {
auth: {
displayName: 'Test User',
email: '[email protected]',
Expand Down Expand Up @@ -79,7 +79,7 @@ describeWithEmulators('function: createInvitation', (env) => {
})

it('should not create an invitation without authentication', () => {
const input: z.output<typeof createInvitationInputSchema> = {
const input: z.input<typeof createInvitationInputSchema> = {
auth: {
displayName: 'Test User',
},
Expand Down
7 changes: 5 additions & 2 deletions functions/src/functions/deleteInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
// SPDX-License-Identifier: MIT
//

import { deleteInvitationInputSchema } from '@stanfordbdhg/engagehf-models'
import {
deleteInvitationInputSchema,
type DeleteInvitationOutput,
} from '@stanfordbdhg/engagehf-models'
import { validatedOnCall } from './helpers.js'
import { UserRole } from '../services/credential/credential.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const deleteInvitation = validatedOnCall(
'deleteInvitation',
deleteInvitationInputSchema,
async (request): Promise<void> => {
async (request): Promise<DeleteInvitationOutput> => {
const factory = getServiceFactory()
const credential = factory.credential(request.auth)
const userService = factory.user()
Expand Down
7 changes: 5 additions & 2 deletions functions/src/functions/deleteUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
// SPDX-License-Identifier: MIT
//

import { deleteUserInputSchema } from '@stanfordbdhg/engagehf-models'
import {
deleteUserInputSchema,
type DeleteUserOutput,
} from '@stanfordbdhg/engagehf-models'
import { validatedOnCall } from './helpers.js'
import { UserRole } from '../services/credential/credential.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const deleteUser = validatedOnCall(
'deleteUser',
deleteUserInputSchema,
async (request): Promise<void> => {
async (request): Promise<DeleteUserOutput> => {
const factory = getServiceFactory()
const credential = factory.credential(request.auth)
const userService = factory.user()
Expand Down
7 changes: 5 additions & 2 deletions functions/src/functions/dismissMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
// SPDX-License-Identifier: MIT
//

import { dismissMessageInputSchema } from '@stanfordbdhg/engagehf-models'
import {
dismissMessageInputSchema,
type DismissMessageOutput,
} from '@stanfordbdhg/engagehf-models'
import { validatedOnCall } from './helpers.js'
import { UserRole } from '../services/credential/credential.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const dismissMessage = validatedOnCall(
'dismissMessage',
dismissMessageInputSchema,
async (request): Promise<void> => {
async (request): Promise<DismissMessageOutput> => {
const factory = getServiceFactory()
const credential = factory.credential(request.auth)
const userId = request.data.userId ?? credential.userId
Expand Down
3 changes: 2 additions & 1 deletion functions/src/functions/exportHealthSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {
exportHealthSummaryInputSchema,
type ExportHealthSummaryOutput,
QuantityUnit,
} from '@stanfordbdhg/engagehf-models'
import { https } from 'firebase-functions/v2'
Expand All @@ -19,7 +20,7 @@ import { getServiceFactory } from '../services/factory/getServiceFactory.js'
export const exportHealthSummary = validatedOnCall(
'exportHealthSummary',
exportHealthSummaryInputSchema,
async (request): Promise<{ content: string }> => {
async (request): Promise<ExportHealthSummaryOutput> => {
const factory = getServiceFactory()
const credential = factory.credential(request.auth)

Expand Down
26 changes: 11 additions & 15 deletions functions/src/functions/getUsersInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@

import {
getUsersInformationInputSchema,
type getUsersInformationOutputSchema,
type GetUsersInformationOutput,
userAuthConverter,
userConverter,
type userInformationSchema,
} from '@stanfordbdhg/engagehf-models'
import { https } from 'firebase-functions'
import { type z } from 'zod'
import { validatedOnCall } from './helpers.js'
import { UserRole } from '../services/credential/credential.js'
import { getServiceFactory } from '../services/factory/getServiceFactory.js'

export const getUsersInformation = validatedOnCall(
'getUsersInformation',
getUsersInformationInputSchema,
async (request): Promise<z.input<typeof getUsersInformationOutputSchema>> => {
async (request): Promise<GetUsersInformationOutput> => {
const factory = getServiceFactory()
const credential = factory.credential(request.auth)
const userService = factory.user()

const result: z.input<typeof getUsersInformationOutputSchema> = {}
const result: GetUsersInformationOutput = {}
for (const userId of request.data.userIds) {
try {
const userData = await userService.getUser(userId)
Expand All @@ -43,18 +42,15 @@ export const getUsersInformation = validatedOnCall(
)

const user = await userService.getAuth(userId)
const userInformation: z.input<typeof userInformationSchema> = {
auth: {
displayName: user.displayName,
email: user.email,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
result[userId] = {
data: {
auth: userAuthConverter.value.encode(user),
user:
request.data.includeUserData && userData !== undefined ?
userConverter.value.encode(userData.content)
: undefined,
},
}
if (request.data.includeUserData && userData !== undefined) {
userInformation.user = userConverter.value.encode(userData.content)
}
result[userId] = { data: userInformation }
} catch (error) {
if (error instanceof https.HttpsError) {
result[userId] = {
Expand Down
Loading