Source contract and expected records
This is an original, fully synthetic mapping example. Inputs are plain, acyclic JSON-shaped records with the fields shown; the canonical shape and rejection codes belong to this example. Adapt the contract before using a different source.
This synthetic format accepts a plus prefix of one to four digits, subscriber digits with ASCII spaces, and an optional digit-string extension. Remove subscriber spaces only. Extension 007 remains text; an omitted extension becomes null. The fictional +000 prefix illustrates parsing and does not assert that a number is diallable.
| Case | Input record (JSON) | Expected output (JSON) |
|---|---|---|
| Keep extension separate | {"countryPrefix":"+000","subscriber":"1234 5678","extension":"007"} | {"ok":true,"value":{"countryPrefix":"+000","subscriberDigits":"12345678","extension":"007","sourceSubscriber":"1234 5678"}} |
| Reject non-digit extension | {"countryPrefix":"+000","subscriber":"1234","extension":"x"} | {"ok":false,"error":"EXTENSION_FORMAT"} |
| Preserve no extension | {"countryPrefix":"+000","subscriber":"1234"} | {"ok":true,"value":{"countryPrefix":"+000","subscriberDigits":"1234","extension":null,"sourceSubscriber":"1234"}} |
Run the local transformation
Run the complete example as a JavaScript module in Node.js. It contains the pure mapping function, all three inputs and literal expected outputs; assertions stop on a mismatch. A rejected case returns ok=false with an error code, rather than a partial canonical record.
import assert from 'node:assert/strict';
function phoneExtension(i){
const fail=error=>({ok:false,error});if(!i||typeof i.countryPrefix!=='string'||!/^\+\d{1,4}$/.test(i.countryPrefix)||typeof i.subscriber!=='string'||!/^[0-9 ]+$/.test(i.subscriber))return fail('PHONE_PART');
const subscriberDigits=i.subscriber.replace(/ /g,'');if(!subscriberDigits)return fail('PHONE_PART');
if(i.extension!==undefined&&(typeof i.extension!=='string'||!/^\d+$/.test(i.extension)))return fail('EXTENSION_FORMAT');
return {ok:true,value:{countryPrefix:i.countryPrefix,subscriberDigits,extension:i.extension??null,sourceSubscriber:i.subscriber}};
}
// Original synthetic cases; expected outputs are literal records.
const cases = [
{
"label": "Keep extension separate",
"input": {
"countryPrefix": "+000",
"subscriber": "1234 5678",
"extension": "007"
},
"expected": {
"ok": true,
"value": {
"countryPrefix": "+000",
"subscriberDigits": "12345678",
"extension": "007",
"sourceSubscriber": "1234 5678"
}
}
},
{
"label": "Reject non-digit extension",
"input": {
"countryPrefix": "+000",
"subscriber": "1234",
"extension": "x"
},
"expected": {
"ok": false,
"error": "EXTENSION_FORMAT"
}
},
{
"label": "Preserve no extension",
"input": {
"countryPrefix": "+000",
"subscriber": "1234"
},
"expected": {
"ok": true,
"value": {
"countryPrefix": "+000",
"subscriberDigits": "1234",
"extension": null,
"sourceSubscriber": "1234"
}
}
}
];
for (const sample of cases) {
const actual = phoneExtension(sample.input);
assert.deepEqual(actual, sample.expected);
console.log(sample.label, JSON.stringify(actual));
}
Work through the boundary
Should 007 be appended to 12345678 or converted to 7?
Show the worked answer
Neither. subscriberDigits is "12345678" and extension is "007". The source subscriber "1234 5678" is retained separately; the extension is a distinct routing part.