Skip to content

🐛 createApi's around() clobbers fields the partial leaves out #1252

Description

@taras

around() in www/context/context-api.ts accepts a Partial<Around<A>>, but it
rebuilds every field of the api rather than only the ones the caller
supplied. For an omitted field, around[field] is undefined, and the
replacement middleware closes over it anyway:

yield* context.set(fields.reduce((sum, field) => {
  let prior = current[field] as Middleware<any[], any>;
  let middleware = around[field] as Middleware<any[], any>;   // undefined
  return Object.assign(sum, {
    [field]: (args: any, next: any) =>
      middleware(args, (...args) => prior(args, next)),        // throws
  });
}, Object.assign({}, current)));

The override itself appears to work. The failure comes later, when something
calls one of the fields that was not overridden:

TypeError: middleware is not a function
    at context-api.ts:67:11

Reproduction

interface Demo {
  a(): Operation<string>;
  b(): Operation<string>;
}

const demo = createApi<Demo>("demo", {
  *a() { return "a"; },
  *b() { return "b"; },
});

await main(function* () {
  yield* demo.around({
    *a(args, next) { return yield* next(...args); },   // `b` omitted
  });

  yield* demo.operations.a();   // fine
  yield* demo.operations.b();   // TypeError: middleware is not a function
});

Why it has not been hit

Every around() call in the repository happens to supply all of its api's
fields:

  • FetchApi and ProcessApi have a single field each, so a partial is not
    expressible.
  • All four loggerApi.around call sites (context/logging.ts,
    testing/logging.ts) list info, debug, warn and error.

It surfaces as soon as there is a multi-field api where overriding one field is
the natural thing to write. UrlApi in #1250 is the first.

Fix

A field the partial omits should keep the middleware it already has. Since the
reduce seeds with Object.assign({}, current), that is just an early return:

let middleware = around[field] as Middleware<any[], any> | undefined;

if (!middleware) {
  return sum;
}

Filed for the record — the fix is already in #1250 as fc19224a, since that PR
needs it. Happy to split it out into its own PR against v4 if you would rather
it land independently of the url work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions