Skip to content

Commit

Permalink
feat: Add support for providers to validate their options at loading …
Browse files Browse the repository at this point in the history
…time (#8853)

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

* fix missing removal

* fix integration tests

* add tests
  • Loading branch information
adrien2p authored Aug 29, 2024
1 parent b857216 commit 77b874f
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 205 deletions.
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

0 comments on commit 77b874f

Please sign in to comment.