diff --git a/src/errors.ts b/src/errors.ts index 1999248..7d644cc 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -14,8 +14,10 @@ export class ErrInvalidArgument extends Error { } export class ErrBadType extends Error { - public constructor(name: string, type: any, value?: any) { - super(`Bad type of "${name}": ${value}. Expected type: ${type}`); + public constructor(name: string, type: any, value?: any, context?: string) { + super( + `Bad type of "${name}": ${value}. Expected type: ${type}. Context: ${context}` + ); } } @@ -48,3 +50,15 @@ export class ErrContractQuery extends Error { super(`Failed to query contract: Method: ${method} : ${message}`); } } + +export class ErrParamValidation extends Error { + public constructor(message: string) { + super(`Params have validation issues : ${message}`); + } +} + +export class ErrFailedOperation extends Error { + public constructor(method: string, message?: string) { + super(`Failed to perform operation: ${method} : ${message}`); + } +} diff --git a/src/minter.ts b/src/minter.ts index 710b13c..4f979ce 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -28,15 +28,18 @@ import { MinterRequirements } from './interfaces'; import { NFTStorage } from 'nft.storage'; import { File } from '@web-std/file'; import { + checkStatus, checkTraitsUrl, checkUrlIsUp, validateSpecificParamsMint } from './utils'; -// import { -// ErrArgumentNotSet, -// ErrContractQuery, -// ErrFailedOperation -// } from './errors'; +import { + ErrBadType, + ErrContractQuery, + ErrFailedOperation, + ErrNetworkConfig, + ErrParamValidation +} from './errors'; export class DataNftMinter { readonly contract: SmartContract; @@ -51,6 +54,11 @@ export class DataNftMinter { * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ constructor(env: string, timeout: number = 10000) { + if (!(env in EnvironmentsEnum)) { + throw new ErrNetworkConfig( + `Invalid environment: ${env}, Expected: 'devnet' | 'mainnet' | 'testnet'` + ); + } this.env = env; const networkConfig = networkConfiguration[env as EnvironmentsEnum]; this.imageServiceUrl = imageService[env as EnvironmentsEnum]; @@ -114,8 +122,10 @@ export class DataNftMinter { }; return requirements; } else { - throw new Error('Could not retrieve minter contract requirements'); - // throw new ErrContractQuery('Could not retrieve requirements'); + throw new ErrContractQuery( + 'viewMinterRequirements', + returnCode.toString() + ); } } @@ -135,10 +145,10 @@ export class DataNftMinter { const returnValue = firstValue?.valueOf(); return new BooleanValue(returnValue).valueOf(); } else { - throw new Error('Error while retrieving the contract pause state'); - // throw new ErrContractQuery( - // 'Error while retrieving the contract pause state' - // ); + throw new ErrContractQuery( + 'viewContractPauseState', + returnCode.toString() + ); } } @@ -247,27 +257,15 @@ export class DataNftMinter { }); if (!allPassed) { - throw new Error(`Params have validation issues = ${validationMessages}`); - // throw new ErrFailedOperation( - // this.mint.name, - // new Error(`params have validation issues = ${validationMessages}`) - // ); + throw new ErrParamValidation(validationMessages); } // E: run any format specific validation... // deep validate all mandatory URLs - try { - await checkUrlIsUp(dataStreamUrl, [200, 403]); - await checkUrlIsUp(dataPreviewUrl, [200]); - await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.mint.name, error); - // } else { - // throw new ErrFailedOperation(this.mint.name); - // } - } + + await checkUrlIsUp(dataStreamUrl, [200, 403]); + await checkUrlIsUp(dataPreviewUrl, [200]); + await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); let imageOnIpfsUrl: string; let metadataOnIpfsUrl: string; @@ -277,13 +275,12 @@ export class DataNftMinter { if (!imageUrl) { if (!nftStorageToken) { - throw new Error( + throw new ErrBadType( + 'nftStorageToken', + 'string', + nftStorageToken, 'NFT Storage token is required when not using custom image and traits' ); - // throw new ErrArgumentNotSet( - // 'nftStorageToken', - // 'NFT Storage token is required when not using custom image and traits' - // ); } const { image, traits } = await this.createFileFromUrl( `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, @@ -302,11 +299,12 @@ export class DataNftMinter { metadataOnIpfsUrl = metadataIpfsUrl; } else { if (!traitsUrl) { - throw new Error('Traits URL is required when using custom image'); - // throw new ErrArgumentNotSet( - // 'traitsUrl', - // 'Traits URL is required when using custom image' - // ); + throw new ErrBadType( + 'traitsUrl', + 'string', + traitsUrl, + 'Traits URL is required when using custom image' + ); } await checkTraitsUrl(traitsUrl); @@ -374,29 +372,21 @@ export class DataNftMinter { body: JSON.stringify({ dataNFTStreamUrl }) }; - try { - const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); - const data = await res.json(); + const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); + const data = await res.json(); - if (data && data.encryptedMessage && data.messageHash) { - return { - dataNftHash: data.messageHash, - dataNftStreamUrlEncrypted: data.encryptedMessage - }; - } else { - throw new Error('Issue with data marshal generate payload'); - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - } - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation( - // this.dataNFTDataStreamAdvertise.name, - // error - // ); - // } else { - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - // } + checkStatus(res); + + if (data && data.encryptedMessage && data.messageHash) { + return { + dataNftHash: data.messageHash, + dataNftStreamUrlEncrypted: data.encryptedMessage + }; + } else { + throw new ErrFailedOperation( + 'dataNFTDataStreamAdvertise', + 'Invalid response from Data Marshal' + ); } } @@ -412,9 +402,8 @@ export class DataNftMinter { }); const dir = [image, traits]; res = await nftstorage.storeDirectory(dir); - } catch (error) { - throw error; - // throw new ErrFailedOperation(this.storeToIpfs.name); + } catch (error: any) { + throw new ErrFailedOperation('storeToIpfs', error.message); } return { imageOnIpfsUrl: `https://ipfs.io/ipfs/${res}/image.png`, diff --git a/src/utils.ts b/src/utils.ts index 3c65aa5..35fbb49 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -528,14 +528,12 @@ export async function checkUrlIsUp(url: string, expectedHttpCodes: number[]) { const response = await fetch(url); if (!expectedHttpCodes.includes(response.status)) { - throw new Error( - `URL needs to return a 200 OK response code (or a 403 Forbidden error code is also allowed for protected Data Streams). url : ${url}, actual HTTP status: ${response.status}` - ); + throw new ErrFetch(response.status, response.statusText); } } export function checkStatus(response: Response) { - if (response.status != 200) { + if (response.status >= 200 && response.status <= 299) { throw new ErrFetch(response.status, response.statusText); } }