-
Notifications
You must be signed in to change notification settings - Fork 0
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: param validation for all RPC methods #29
base: master
Are you sure you want to change the base?
Conversation
a6f68cf
to
5fcf239
Compare
name: z.string().min(1), | ||
symbol: z.string().min(1), | ||
amount: z.number().positive(), | ||
address: z.string().nullable().optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should use .nullish()
instead of .nullable().optional()
amount: z.number().positive(), | ||
address: z.string().nullable().optional(), | ||
change_address: z.string().nullable().optional(), | ||
create_mint: z.boolean().optional().default(true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_mint: z.boolean().optional().default(true), | |
create_mint: z.boolean().default(true), |
The deault already makes the field optional.
> foo = z.object({ bar: z.number().default(123) })
> foo.parse({})
{ bar: 123 }
name: params.name, | ||
symbol: params.symbol, | ||
amount: params.amount, | ||
address: params.address ?? null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use the null
as default?
symbol: params.symbol, | ||
amount: params.amount, | ||
address: params.address ?? null, | ||
changeAddress: params.change_address ?? null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should add a transform on the schema.
const foo = z.object({
// schema definition
}).transform(input => ({
name: input.name,
address: input.address,
changeAddress: input.change_address,
// ... other keys
}));
This way when we parse the object it already comes out as camelcase.
params.name, | ||
params.symbol, | ||
params.amount, | ||
{ | ||
changeAddress: params.change_address, | ||
address: params.address, | ||
createMint: params.create_mint, | ||
mintAuthorityAddress: params.mint_authority_address, | ||
allowExternalMintAuthorityAddress: params.allow_external_mint_authority_address, | ||
createMelt: params.create_melt, | ||
meltAuthorityAddress: params.melt_authority_address, | ||
allowExternalMeltAuthorityAddress: params.allow_external_melt_authority_address, | ||
data: params.data, | ||
pinCode: pinCodeResponse.data.pinCode, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): Maybe we should add this to the transform.
We could transform the data to be a params.options
with these fields, which would make the call something like:
wallet.createNewToken(params.name, params.symbol, params.amount, params.options);
This would make any changes (new arguments, etc.) only need to be changed on the schema and transform.
But this is just a suggestion, maybe the flat object is better for testing or using elsewhere.
}).refine( | ||
(data) => { | ||
if (data.type === 'index') { | ||
return data.index !== undefined; | ||
} | ||
if (data.type === 'full_path') { | ||
return data.full_path !== undefined; | ||
} | ||
return true; | ||
}, | ||
{ | ||
message: "index is required when type is 'index', full_path is required when type is 'full_path'", | ||
} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}).refine( | |
(data) => { | |
if (data.type === 'index') { | |
return data.index !== undefined; | |
} | |
if (data.type === 'full_path') { | |
return data.full_path !== undefined; | |
} | |
return true; | |
}, | |
{ | |
message: "index is required when type is 'index', full_path is required when type is 'full_path'", | |
} | |
); | |
}).refine( | |
(data) => { | |
return (data.type !== 'index' || data.index !== undefined); | |
}, | |
{ | |
message: "index is required when type is 'index'", | |
} | |
).refine( | |
(data) => { | |
return (data.type !== 'full_path' || data.full_path !== undefined); | |
}, | |
{ | |
message: "full_path is required when type is 'full_path'", | |
} | |
); |
We can chain the refines so the message is more descriptive of the case
const getAddressSchema = z.object({ | ||
type: z.enum(['first_empty', 'full_path', 'index', 'client']), | ||
index: z.number().int().nonnegative().optional(), | ||
full_path: z.string().min(1).optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know fullpath is not implemented, but are we going to expect a m/44'/280'/0'/0/index
?
If so, maybe we should validate this here, or at least comment that we should add validation later
const getAddressSchema = z.object({ | ||
type: z.enum(['first_empty', 'full_path', 'index', 'client']), | ||
index: z.number().int().nonnegative().optional(), | ||
full_path: z.string().min(1).optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This schema of type + optional fields may be better expressed as a discriminated union.
This would make the refines obsolete since in the index
type the index argument would be required.
try { | ||
const params = getBalanceSchema.parse(rpcRequest.params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't want to put the entire function inside a try catch and handle parsing errors at the end you could use safeParse
which returns a success flag with the data or error.
What do you think?
@@ -20,7 +21,20 @@ import { | |||
RpcResponse, | |||
SendNanoContractTxLoadingFinishedTrigger, | |||
} from '../types'; | |||
import { PromptRejectedError, SendNanoContractTxError } from '../errors'; | |||
import { PromptRejectedError, SendNanoContractTxError, InvalidParamsError } from '../errors'; | |||
import { NanoContractAction } from '@hathor/wallet-lib/lib/nano_contracts/types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should expose these types in the lib index instead of importing from the internal path.
Acceptance Criteria
Checklist
master
, confirm this code is production-ready and can be included in future releases as soon as it gets merged