gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated: amount regex and dura


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: amount regex and duration fields
Date: Fri, 12 Feb 2021 06:43:20 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository merchant-backoffice.

The following commit(s) were added to refs/heads/master by this push:
     new 5002a44  amount regex and duration fields
5002a44 is described below

commit 5002a444a7d207295457cf6c21e5192c5c49c59c
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Feb 12 02:43:11 2021 -0300

    amount regex and duration fields
---
 src/components/yup/YupInput.tsx      | 40 ++++++++++-------
 src/constants.ts                     |  2 +-
 src/i18n/index.ts                    | 12 +++++
 src/routes/instances/CreateModal.tsx | 34 ++-------------
 src/routes/instances/UpdateModal.tsx | 24 ++--------
 src/schemas/index.ts                 | 85 ++++++++++++++++++++++++++++++++++++
 tests/functions/regex.test.ts        | 85 ++++++++++++++++++++++++++----------
 7 files changed, 192 insertions(+), 90 deletions(-)

diff --git a/src/components/yup/YupInput.tsx b/src/components/yup/YupInput.tsx
index b10ff22..faf8da4 100644
--- a/src/components/yup/YupInput.tsx
+++ b/src/components/yup/YupInput.tsx
@@ -3,30 +3,38 @@ import { Text, useText } from "preact-i18n";
 
 interface Props {
   name: string;
-  value: any;
+  object: any;
   info: any;
   errors: any;
   valueHandler: any;
 }
 
-export default function YupInput({ name, info, value, errors, valueHandler }: 
Props): VNode {
-  const dict = useText({placeholder: `fields.instance.${name}.placeholder`})
+function convert(object: any, name: string, type?: string): any {
+  switch (type) {
+    case 'duration': return object[name]?.d_ms;
+    default: return object[name];
+  }
+}
+
+export default function YupInput({ name, info, object, errors, valueHandler }: 
Props): VNode {
+  const dict = useText({ placeholder: `fields.instance.${name}.placeholder` })
+  const value = convert(object, name, info.meta?.type)
 
   return <div class="field is-horizontal">
-  <div class="field-label is-normal">
-    <label class="label"><Text id={`fields.instance.${name}.label`} /></label>
-  </div>
-  <div class="field-body">
-    <div class="field">
-      <p class="control is-expanded has-icons-left">
-        <input class="input" type="text" 
-            placeholder={dict['placeholder']} readonly={info?.meta?.readonly} 
-            name={name} value={value[name]} 
+    <div class="field-label is-normal">
+      <label class="label"><Text id={`fields.instance.${name}.label`} 
/></label>
+    </div>
+    <div class="field-body">
+      <div class="field">
+        <p class="control is-expanded has-icons-left">
+          <input class="input" type="text"
+            placeholder={dict['placeholder']} readonly={info?.meta?.readonly}
+            name={name} value={value}
             onChange={(e): void => valueHandler((prev: any) => ({ ...prev, 
[name]: e.currentTarget.value }))} />
-        <Text id={`fields.instance.${name}.help`} />
-      </p>
-      {errors[name] ? <p class="help is-danger"><Text 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+          <Text id={`fields.instance.${name}.help`} />
+        </p>
+        {errors[name] ? <p class="help is-danger"><Text 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+      </div>
     </div>
   </div>
-</div>
 }
diff --git a/src/constants.ts b/src/constants.ts
index fec7f7c..826aa61 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,2 +1,2 @@
 export const 
PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]*(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
-
+export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 9cb060d..7a99719 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -66,6 +66,12 @@ export default {
         default_wire_fee_amortization: {
           label: 'AmortizaciĆ³n de pago',
         },
+        default_pay_delay: {
+          label: 'Tiempo de espera de pago'
+        },
+        default_wire_transfer_delay: {
+          label: 'Tiempo de espera de transferencia bancaria'
+        },
       },
     },
     validation: {
@@ -149,6 +155,12 @@ export default {
         default_wire_fee_amortization: {
           label: 'Max fee amortization',
         },
+        default_pay_delay: {
+          label: 'Pay delay'
+        },
+        default_wire_transfer_delay: {
+          label: 'Wire transfer delay'
+        },
       }
     },
     validation: {
diff --git a/src/routes/instances/CreateModal.tsx 
b/src/routes/instances/CreateModal.tsx
index b66b483..d9e9828 100644
--- a/src/routes/instances/CreateModal.tsx
+++ b/src/routes/instances/CreateModal.tsx
@@ -1,38 +1,10 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { Duration, MerchantBackend } from "../../declaration";
+import { MerchantBackend } from "../../declaration";
 import * as yup from 'yup';
 import ConfirmModal from "../../components/modal";
 import YupInput from "../../components/yup/YupInput"
-
-function stringToArray(this: yup.AnySchema, current: any, original: string): 
string[] {
-  if (this.isType(current)) return current;
-  return original.split(',').filter(s => s.length > 0)
-}
-function numberToDuration(this: yup.AnySchema, current: any, original: 
string): Duration {
-  if (this.isType(current)) return current;
-  const d_ms = parseInt(original, 10)
-  return { d_ms }
-}
-/*
-validations
-  * delays size
-  * payto-uri format
-  * currency
-*/
-
-const schema = yup.object().shape({
-  id: yup.string().required().label("Id"),
-  name: yup.string().required().label("Name"),
-  payto_uris: yup.array().of(yup.string())
-    .min(1).label("Payment Method")
-    .transform(stringToArray),
-  default_max_deposit_fee: yup.string().required().label("Max Deposit Fee"),
-  default_max_wire_fee: yup.string().required().label("Max Wire"),
-  default_wire_fee_amortization: yup.number().required().label("WireFee 
Amortization"),
-  // default_pay_delay: yup.number().required().label("Pay 
delay").transform(numberToDuration),
-  // default_wire_transfer_delay: yup.number().required().label("Wire transfer 
Delay").transform(numberToDuration),
-});
+import { InstanceCreateSchema as schema } from '../../schemas'
 
 interface Props {
   onCancel: () => void;
@@ -61,7 +33,7 @@ export default function CreateModal({ onCancel, onConfirm }: 
Props): VNode {
   return <ConfirmModal description="create_instance" active onConfirm={submit} 
onCancel={onCancel}>
     {Object.keys(schema.fields).map(f => {
       const info = schema.fields[f].describe()
-      return <YupInput name={f} info={info} errors={errors} value={value} 
valueHandler={valueHandler} />
+      return <YupInput name={f} info={info} errors={errors} object={value} 
valueHandler={valueHandler} />
     })}
 
   </ConfirmModal>
diff --git a/src/routes/instances/UpdateModal.tsx 
b/src/routes/instances/UpdateModal.tsx
index 9fc1309..8340465 100644
--- a/src/routes/instances/UpdateModal.tsx
+++ b/src/routes/instances/UpdateModal.tsx
@@ -4,23 +4,7 @@ import { MerchantBackend } from "../../declaration";
 import * as yup from 'yup';
 import ConfirmModal from '../../components/modal'
 import YupInput from "../../components/yup/YupInput";
-import { PAYTO_REGEX } from "../../constants";
-
-function stringToArray(this: yup.AnySchema, current: any, original: string): 
string[] {
-  if (this.isType(current)) return current;
-  return original.split(',').filter(s => s.length > 0)
-}
-
-const schema = yup.object().shape({
-  name: yup.string().required(),
-  payto_uris: yup.array().of(yup.string()).min(1)
-    .transform(stringToArray).test('payto','{path} is not valid', (values): 
boolean => !!values && values.filter( v => v && PAYTO_REGEX.test(v) ).length > 
0 ),
-  default_max_deposit_fee: yup.string().required(),
-  default_max_wire_fee: yup.string().required(),
-  default_wire_fee_amortization: yup.number().required(),
-  // default_pay_delay: yup.number().required().label("Pay 
delay").transform(numberToDuration),
-  // default_wire_transfer_delay: yup.number().required().label("Wire transfer 
Delay").transform(numberToDuration),
-});
+import { InstanceUpdateSchema as schema } from '../../schemas'
 
 interface Props {
   element: MerchantBackend.Instances.QueryInstancesResponse | null;
@@ -33,7 +17,7 @@ interface KeyValue {
 }
 
 export default function UpdateModal({ element, onCancel, onConfirm }: Props): 
VNode {
-  const copy: any = !element ? {} : 
Object.keys(schema.fields).reduce((prev,cur) => ({...prev, [cur]: (element as 
any)[cur] }), {})
+  const copy: any = !element ? {} : Object.keys(schema.fields).reduce((prev, 
cur) => ({ ...prev, [cur]: (element as any)[cur] }), {})
 
   const [value, valueHandler] = useState(copy)
   const [errors, setErrors] = useState<KeyValue>({})
@@ -42,7 +26,7 @@ export default function UpdateModal({ element, onCancel, 
onConfirm }: Props): VN
     try {
       schema.validateSync(value, { abortEarly: false })
 
-      onConfirm({...schema.cast(value), address: {}, jurisdiction: {}, 
default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 }} 
as MerchantBackend.Instances.InstanceReconfigurationMessage);
+      onConfirm({ ...schema.cast(value), address: {}, jurisdiction: {} } as 
MerchantBackend.Instances.InstanceReconfigurationMessage);
     } catch (err) {
       const errors = err.inner as yup.ValidationError[]
       const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message 
} }), {})
@@ -54,7 +38,7 @@ export default function UpdateModal({ element, onCancel, 
onConfirm }: Props): VN
     {Object.keys(schema.fields).map(f => {
 
       const info = schema.fields[f].describe()
-      return <YupInput name={f} info={info} errors={errors} value={value} 
valueHandler={valueHandler} />
+      return <YupInput name={f} info={info} errors={errors} object={value} 
valueHandler={valueHandler} />
 
     })}
 
diff --git a/src/schemas/index.ts b/src/schemas/index.ts
new file mode 100644
index 0000000..53cd709
--- /dev/null
+++ b/src/schemas/index.ts
@@ -0,0 +1,85 @@
+import * as yup from 'yup';
+import { AMOUNT_REGEX, PAYTO_REGEX } from "../constants";
+import { Duration } from '../declaration';
+
+yup.setLocale({
+  mixed: {
+    default: 'field_invalid',
+  },
+  number: {
+    min: ({ min }) => ({ key: 'field_too_short', values: { min } }),
+    max: ({ max }) => ({ key: 'field_too_big', values: { max } }),
+  },
+});
+
+function stringToArray(this: yup.AnySchema, current: any, original: string): 
string[] {
+  if (this.isType(current)) return current;
+  return original.split(',').filter(s => s.length > 0)
+}
+
+function listOfPayToUrisAreValid(values?: (string | undefined)[]): boolean {
+  return !!values && values.filter(v => v && PAYTO_REGEX.test(v)).length > 0;
+}
+
+function numberToDuration(this: yup.AnySchema, current: any, original: 
string): Duration {
+  if (this.isType(current)) return current;
+  const d_ms = parseInt(original, 10)
+  return { d_ms }
+}
+
+function currencyWithAmountIsValid(value?: string): boolean {
+  return !!value && AMOUNT_REGEX.test(value)
+}
+
+export const InstanceUpdateSchema = yup.object().shape({
+  name: yup.string().required(),
+  payto_uris: yup.array().of(yup.string())
+    .min(1)
+    .transform(stringToArray)
+    .test('payto', '{path} is not valid', listOfPayToUrisAreValid),
+  default_max_deposit_fee: yup.string()
+    .required()
+    .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+  default_max_wire_fee: yup.string()
+    .required()
+    .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+  default_wire_fee_amortization: yup.number()
+    .required(),
+    default_pay_delay: yup.object()
+    .shape({d_ms: yup.number() })
+    .required()
+    .meta({type:'duration'})
+    .transform(numberToDuration),
+  default_wire_transfer_delay: yup.object()
+    .shape({d_ms: yup.number() })
+    .required()
+    .meta({type:'duration'})
+    .transform(numberToDuration),
+});
+
+export const InstanceCreateSchema = yup.object().shape({
+  id: yup.string().required(),
+  name: yup.string().required(),
+  payto_uris: yup.array().of(yup.string())
+    .min(1)
+    .transform(stringToArray)
+    .test('payto', '{path} is not valid', listOfPayToUrisAreValid),
+  default_max_deposit_fee: yup.string()
+    .required()
+    .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+  default_max_wire_fee: yup.string()
+    .required()
+    .test('amount', '{path} is not valid', currencyWithAmountIsValid),
+  default_wire_fee_amortization: yup.number()
+    .required(),
+  default_pay_delay: yup.object()
+    .shape({d_ms: yup.number() })
+    .required()
+    .meta({type:'duration'})
+    .transform(numberToDuration),
+  default_wire_transfer_delay: yup.object()
+    .shape({d_ms: yup.number() })
+    .required()
+    .meta({type:'duration'})
+    .transform(numberToDuration),
+});
diff --git a/tests/functions/regex.test.ts b/tests/functions/regex.test.ts
index 2c1d63f..696a515 100644
--- a/tests/functions/regex.test.ts
+++ b/tests/functions/regex.test.ts
@@ -1,25 +1,66 @@
-import { PAYTO_REGEX } from "../../src/constants";
+import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/constants";
 
-const valids = [
-  'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello',
-  'payto://ach/122000661/1234',
-  'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200',
-  'payto://void/?amount=EUR:10.5',
-  'payto://ilp/g.acme.bob'
-]
+describe('payto uri format', () => {
+  const valids = [
+    'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello',
+    'payto://ach/122000661/1234',
+    'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200',
+    'payto://void/?amount=EUR:10.5',
+    'payto://ilp/g.acme.bob'
+  ]
+  
+  test('should be valid', () => {
+    valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
+  });
+  
+  const invalids = [
+    // has two question marks
+    'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello',
+    // has a space
+    'payto://ach /122000661/1234',
+    // has a space
+    'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200',
+    // invalid field name (mount instead of amount)
+    'payto://void/?mount=EUR:10.5',
+    // payto:// is incomplete
+    'payto: //ilp/g.acme.bob'
+  ]
+  
+  test('should not be valid', () => {
+    invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
+  });  
+})
 
-test('should be valid', () => {
-  valids.forEach(v => expect(v).toMatch(PAYTO_REGEX))
-});
+describe('amount format', () => {
+  const valids = [
+    'ARS:10',
+    'COL:10.2',
+    'UY:1,000.2',
+    'ARS:10.123,123',
+    'ARS:1,000,000',
+    'ARSCOL:10',
+    'THISISTHEMOTHERCOIN:1,000,000.123,123',
+  ]
+  
+  test('should be valid', () => {
+    valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX))
+  });
+  
+  const invalids = [
+    //no currency name
+    ':10',
+    //use . instead of ,
+    'ARS:1.000.000',
+    //currency name with numbers
+    '1ARS:10',
+    //currency name with numbers
+    'AR5:10',
+    //missing value
+    'USD:',
+  ]
+  
+  test('should not be valid', () => {
+    invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX))
+  });  
 
-const invalids = [
-  'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello',
-  'payto://ach/122000661 /1234',
-  'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200',
-  'payto://void/?mount=EUR:10.5',
-  'payto: //ilp/g.acme.bob'
-]
-
-test('should not be valid', () => {
-  invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX))
-});
+})
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]