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

feat: Add support for providers to validate their options at loading time #8853

Merged
merged 6 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const service = class TestService {
static validateOptions(options: Record<any, any>) {
throw new Error("Wrong options")
}
}

export default {
services: [service],
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createMedusaContainer } from "@medusajs/utils"
import { Lifetime, asFunction } from "awilix"
import { asFunction, Lifetime } from "awilix"
import { moduleProviderLoader } from "../module-provider-loader"

const logger = {
Expand Down Expand Up @@ -34,6 +34,24 @@ describe("modules loader", () => {
expect(testService.constructor.name).toEqual("TestService")
})

it("should fail to register the provider service", async () => {
const moduleProviders = [
{
resolve: "@providers/default-with-fail-validation",
id: "default",
options: {},
},
]

const err = await moduleProviderLoader({
container,
providers: moduleProviders,
}).catch((e) => e)

expect(err).toBeTruthy()
expect(err.message).toBe("Wrong options")
})

it("should register the provider service with custom register fn", async () => {
const fn = async (klass, container, details) => {
container.register({
Expand Down Expand Up @@ -64,6 +82,35 @@ describe("modules loader", () => {
expect(testService.constructor.name).toEqual("TestService")
})

it("should fail to register the provider service with custom register fn", async () => {
const fn = async (klass, container, details) => {
container.register({
[`testServiceCustomRegistration`]: asFunction(
(cradle) => new klass(cradle, details.options),
{
lifetime: Lifetime.SINGLETON,
}
),
})
}
const moduleProviders = [
{
resolve: "@providers/default-with-fail-validation",
id: "default",
options: {},
},
]

const err = await moduleProviderLoader({
container,
providers: moduleProviders,
registerServiceFn: fn,
}).catch((e) => e)

expect(err).toBeTruthy()
expect(err.message).toBe("Wrong options")
})

it("should log the errors if no service is defined", async () => {
const moduleProviders = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ export async function loadModuleProvider(
)
}

const services = await promiseAll(
return await promiseAll(
loadedProvider.services.map(async (service) => {
// Ask the provider to validate its options
await service.validateOptions?.(provider.options)

const name = lowerCaseFirst(service.name)
if (registerServiceFn) {
// Used to register the specific type of service in the provider
Expand All @@ -83,6 +86,4 @@ export async function loadModuleProvider(
return service
})
)

return services
}
9 changes: 8 additions & 1 deletion packages/core/utils/src/auth/abstract-auth-provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
AuthIdentityProviderService,
AuthenticationInput,
AuthenticationResponse,
AuthIdentityProviderService,
IAuthProvider,
} from "@medusajs/types"

Expand Down Expand Up @@ -74,6 +74,7 @@ export abstract class AbstractAuthModuleProvider implements IAuthProvider {
* @ignore
*/
private static DISPLAY_NAME: string

/**
* @ignore
*/
Expand All @@ -91,6 +92,12 @@ export abstract class AbstractAuthModuleProvider implements IAuthProvider {
return (this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME
}

/**
* Override this static method in order for the loader to validate the options provided to the module provider.
* @param options
*/
static validateOptions(options: Record<any, any>): void | never {}

/**
* @ignore
*
Expand Down
50 changes: 28 additions & 22 deletions packages/core/utils/src/file/abstract-file-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,46 @@ import { FileTypes, IFileProvider } from "@medusajs/types"

/**
* ### constructor
*
*
* The constructor allows you to access resources from the module's container using the first parameter,
* and the module's options using the second parameter.
*
*
* If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
*
*
* #### Example
*
*
* ```ts
* import { Logger } from "@medusajs/types"
* import { AbstractFileProviderService } from "@medusajs/utils"
*
*
* type InjectedDependencies = {
* logger: Logger
* }
*
*
* type Options = {
* apiKey: string
* }
*
*
* class MyFileProviderService extends AbstractFileProviderService {
* protected logger_: Logger
* protected options_: Options
* // assuming you're initializing a client
* protected client
*
*
* constructor (
* { logger }: InjectedDependencies,
* options: Options
* ) {
* super()
*
*
* this.logger_ = logger
* this.options_ = options
*
*
* // assuming you're initializing a client
* this.client = new Client(options)
* }
* }
*
*
* export default MyFileProviderService
* ```
*/
Expand All @@ -51,6 +51,12 @@ export class AbstractFileProviderService implements IFileProvider {
*/
static identifier: string

/**
* Override this static method in order for the loader to validate the options provided to the module provider.
* @param options
*/
static validateOptions(options: Record<any, any>): void | never {}

/**
* @ignore
*/
Expand All @@ -60,10 +66,10 @@ export class AbstractFileProviderService implements IFileProvider {

/**
* This method uploads a file using your provider's custom logic.
*
*
* @param {FileTypes.ProviderUploadFileDTO} file - The file to upload
* @returns {Promise<FileTypes.ProviderFileResultDTO>} The uploaded file's details.
*
*
* @example
* class MyFileProviderService extends AbstractFileProviderService {
* // ...
Expand All @@ -72,7 +78,7 @@ export class AbstractFileProviderService implements IFileProvider {
* ): Promise<ProviderFileResultDTO> {
* // TODO upload file to third-party provider
* // or using custom logic
*
*
* return {
* url: "some-url.com",
* key: "file-name"
Expand All @@ -88,10 +94,10 @@ export class AbstractFileProviderService implements IFileProvider {

/**
* This method deletes the file from storage.
*
*
* @param {FileTypes.ProviderDeleteFileDTO} file - The details of the file to delete.
* @returns {Promise<void>} Resolves when the file is deleted.
*
*
* @example
* class MyFileProviderService extends AbstractFileProviderService {
* // ...
Expand All @@ -108,16 +114,16 @@ export class AbstractFileProviderService implements IFileProvider {
}

/**
* This method is used to retrieve a download URL of the file. For some providers,
* This method is used to retrieve a download URL of the file. For some providers,
* such as S3, a presigned URL indicates a temporary URL to get access to a file.
*
* If your provider doesn’t perform or offer a similar functionality, you can
*
* If your provider doesn’t perform or offer a similar functionality, you can
* return the URL to download the file.
*
* @param {FileTypes.ProviderGetFileDTO} fileData - The details of the file to get its
*
* @param {FileTypes.ProviderGetFileDTO} fileData - The details of the file to get its
* presigned URL.
* @returns {Promise<string>} The file's presigned URL.
*
*
* @example
* class MyFileProviderService extends AbstractFileProviderService {
* // ...
Expand Down
6 changes: 6 additions & 0 deletions packages/core/utils/src/fulfillment/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export class AbstractFulfillmentProviderService
return obj?.constructor?._isFulfillmentService
}

/**
* Override this static method in order for the loader to validate the options provided to the module provider.
* @param options
*/
static validateOptions(options: Record<any, any>): void | never {}

getIdentifier() {
return (this.constructor as any).identifier
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,74 @@
import { NotificationTypes, INotificationProvider } from "@medusajs/types"
import { INotificationProvider, NotificationTypes } from "@medusajs/types"

/**
* ### constructor
*
*
* The constructor allows you to access resources from the module's container using the first parameter,
* and the module's options using the second parameter.
*
*
* If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
*
*
* #### Example
*
*
* ```ts
* import { AbstractNotificationProviderService } from "@medusajs/utils"
* import { Logger } from "@medusajs/types"
*
*
* type InjectedDependencies = {
* logger: Logger
* }
*
*
* type Options = {
* apiKey: string
* }
*
*
* class MyNotificationProviderService extends AbstractNotificationProviderService {
* protected logger_: Logger
* protected options_: Options
* // assuming you're initializing a client
* protected client
*
*
* constructor (
* { logger }: InjectedDependencies,
* options: Options
* ) {
* super()
*
*
* this.logger_ = logger
* this.options_ = options
*
*
* // assuming you're initializing a client
* this.client = new Client(options)
* }
* }
*
*
* export default MyNotificationProviderService
* ```
*/
export class AbstractNotificationProviderService
implements INotificationProvider
{
/**
* Override this static method in order for the loader to validate the options provided to the module provider.
* @param options
*/
static validateOptions(options: Record<any, any>): void | never {}

/**
* This method is used to send a notification using the third-party provider or your custom logic.
*
*
* @param {NotificationTypes.ProviderSendNotificationDTO} notification - The details of the
* notification to send.
* @returns {Promise<NotificationTypes.ProviderSendNotificationResultsDTO>} The result of sending
* the notification.
*
*
* @example
* // other imports...
* import {
* import {
* ProviderSendNotificationDTO,
* ProviderSendNotificationResultsDTO
* } from "@medusajs/types"
*
*
* class MyNotificationProviderService extends AbstractNotificationProviderService {
* // ...
* async send(
Expand Down
Loading
Loading