Skip to content
Astera Technology

Map goods and service source lines to different record shapes

Use an explicit source type to choose quantity or deliverables.

Download complete case notes and answer keys ↓

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 original two-format source declares type goods or service plus a description. Goods require nonnegative safe whole units and a nonempty unitLabel, mapped to quantity. Services require a deliverables string list and must omit units, mapped to deliverables. These are local source constraints, not a general rule about all service sales.

Editable synthetic mapping cases
CaseInput record (JSON)Expected output (JSON)
Construct goods quantity record{"type":"goods","description":"Panels","units":3,"unitLabel":"piece"}{"ok":true,"value":{"kind":"goods","description":"Panels","quantity":{"units":3,"unit":"piece"}}}
Reject physical units on service shape{"type":"service","description":"Setup","units":3,"deliverables":["Guide"]}{"ok":false,"error":"SERVICE_QUANTITY"}
Preserve service deliverable list{"type":"service","description":"Setup","deliverables":[]}{"ok":true,"value":{"kind":"service","description":"Setup","deliverables":[]}}
Download editable CSV ↓

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.

Complete function and executable cases
import assert from 'node:assert/strict';

function goodsServiceLine(i){
  const fail=error=>({ok:false,error});if(!i||typeof i.description!=='string'||!i.description)return fail('LINE_DESCRIPTION');
  if(i.type==='goods'){
    if(!Number.isSafeInteger(i.units)||i.units<0||typeof i.unitLabel!=='string'||!i.unitLabel)return fail('GOODS_QUANTITY');
    return {ok:true,value:{kind:'goods',description:i.description,quantity:{units:i.units,unit:i.unitLabel}}};
  }
  if(i.type==='service'){
    if(Object.hasOwn(i,'units'))return fail('SERVICE_QUANTITY');
    if(!Array.isArray(i.deliverables)||i.deliverables.some(x=>typeof x!=='string'||!x))return fail('SERVICE_DELIVERABLES');
    return {ok:true,value:{kind:'service',description:i.description,deliverables:[...i.deliverables]}};
  }
  return fail('LINE_TYPE');
}

// Original synthetic cases; expected outputs are literal records.
const cases = [
  {
    "label": "Construct goods quantity record",
    "input": {
      "type": "goods",
      "description": "Panels",
      "units": 3,
      "unitLabel": "piece"
    },
    "expected": {
      "ok": true,
      "value": {
        "kind": "goods",
        "description": "Panels",
        "quantity": {
          "units": 3,
          "unit": "piece"
        }
      }
    }
  },
  {
    "label": "Reject physical units on service shape",
    "input": {
      "type": "service",
      "description": "Setup",
      "units": 3,
      "deliverables": [
        "Guide"
      ]
    },
    "expected": {
      "ok": false,
      "error": "SERVICE_QUANTITY"
    }
  },
  {
    "label": "Preserve service deliverable list",
    "input": {
      "type": "service",
      "description": "Setup",
      "deliverables": []
    },
    "expected": {
      "ok": true,
      "value": {
        "kind": "service",
        "description": "Setup",
        "deliverables": []
      }
    }
  }
];
for (const sample of cases) {
  const actual = goodsServiceLine(sample.input);
  assert.deepEqual(actual, sample.expected);
  console.log(sample.label, JSON.stringify(actual));
}
Download runnable example ↓

Work through the boundary

What happens to a service with an empty deliverables list?

Show the worked answer

It remains a service record with deliverables=[]. A service that also supplies units is rejected as SERVICE_QUANTITY; the mapper does not reinterpret it as goods.

Related resources