← Adapter lesson
Lab

Adapter

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

// The dashboard's contract: a permit, null for "no permit", or an error
// saying why the source could not answer.
type Permit = { plate: string; zone: string; expiresAt: string };
// The source could not answer: ask again later.
class PermitUnavailable extends Error {}
interface PermitLookup {
  lookupPermit(plate: string, zone: string): Permit | null;
}

// Three existing APIs, as they already are.
type LegacyReply = { state: string; registration: string; curb: string; end: string };
interface LegacyMeter {
  lookup(plate: string, curb: string): LegacyReply;
}
type PartnerReply = { status: number; vehicle: string; district: string; paidThrough: string };
interface PartnerMeter {
  fetchPermit(plate: string, zone: string): PartnerReply;
}
// The city's resident-permit registry. It is asked about a plate written
// without dashes (KLM204) and lists every zone that plate holds a permit in.
// A permit ends endsAtMinute minutes after midnight; ok false means the
// registry could not answer.
type ResidentPermit = { zone: string; endsAtMinute: number };
type ResidentReply = { ok: boolean; permits: ResidentPermit[] };
interface ResidentRegistry {
  permitsFor(plate: string): ResidentReply;
}

// Each adapter holds one source and answers the dashboard's question with it.
class LegacyPermitAdapter implements PermitLookup {
  constructor(private source: LegacyMeter) {}
  lookupPermit(plate: string, zone: string): Permit | null {
    const reply = this.source.lookup(plate, zone);
    if (reply.state === 'none') return null;
    if (reply.state !== 'active') throw new PermitUnavailable(reply.state);
    return { plate: reply.registration, zone: reply.curb, expiresAt: reply.end };
  }
}

class PartnerPermitAdapter implements PermitLookup {
  constructor(private source: PartnerMeter) {}
  lookupPermit(plate: string, zone: string): Permit | null {
    const reply = this.source.fetchPermit(plate, zone);
    if (reply.status === 404) return null;
    if (reply.status !== 200) throw new PermitUnavailable(`status ${reply.status}`);
    return { plate: reply.vehicle, zone: reply.district, expiresAt: reply.paidThrough };
  }
}

// First draft, from the pull request that added the registry.
class ResidentPermitAdapter implements PermitLookup {
  constructor(private source: ResidentRegistry) {}
  lookupPermit(plate: string, zone: string): Permit | null {
    const reply = this.source.permitsFor(plate);
    if (!reply.ok) throw new PermitUnavailable('registry');
    const permit = reply.permits[0];
    if (!permit) return null;
    return { plate, zone, expiresAt: String(permit.endsAtMinute) };
  }
}

// The dashboard knows PermitLookup and no provider.
function showPermit(reader: PermitLookup, plate: string, zone: string): string {
  try {
    const permit = reader.lookupPermit(plate, zone);
    if (!permit) return `${plate} in ${zone}: no active permit`;
    return `${permit.plate} in ${permit.zone}: permit through ${permit.expiresAt}`;
  } catch {
    return `${plate} in ${zone}: check again, source unavailable`;
  }
}