question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

guidance following removal of intersection

See original GitHub issue

Hi, with the removal of intersection, I’m wondering what the best way to represent base shapes with conditional extensions? From looking at the diff, I suppose the recommendation is to use merge or extend, but that isn’t very ergonomic with union types…

For example, I have a schema that validates oidc-provider client configurations that looks like this:

const clients = array(
  intersection(
    object({
      client_id: string(),
      grant_types: array(string()),
      redirect_uris: array(string()),
      tos_uri: string().url(),
      policy_uri: string().url(),
    }),
    union([
      object({
        application_type: literal("native"),
        token_endpoint_auth_method: literal("none"),
      }),
      object({
        token_endpoint_auth_method: z
          .enum(["client_secret_basic", "client_secret_post"])
          .optional(),
        client_secret: string(),
      }),
      object({
        token_endpoint_auth_method: z.enum(["private_key_jwt"]),
        jwks: object({
          keys: array(
            object({
              kty: string(),
              kid: string(),
              n: string(),
              e: string(),
            })
          ),
        }),
      }),
    ])
  )
)

It’s a little busy, but you can see that there’s a base schema that intersects with a union type for all of the conditional variants.

Without intersect, it seems like every type in the union has to be explicitly extended:

const clientBase = {
  client_id: string(),
  grant_types: array(string()),
  redirect_uris: array(string()),
  tos_uri: string().url(),
  policy_uri: string().url(),
};
const clients = array(
  union([
    object({
      application_type: literal("native"),
      token_endpoint_auth_method: literal("none"),
    }).extend(clientBase),
    object({
      token_endpoint_auth_method: z
        .enum(["client_secret_basic", "client_secret_post"])
        .optional(),
      client_secret: string(),
    }).extend(clientBase),
    object({
      token_endpoint_auth_method: z.enum(["private_key_jwt"]),
      jwks: object({
        keys: array(
          object({
            kty: string(),
            kid: string(),
            n: string(),
            e: string(),
          })
        ),
      }),
    }).extend(clientBase),
  ])
);

We can’t use [].map() over the union argument because map doesn’t preserve the input length in the output type.

Is there another way of continuing to express this, or is the expectation that every entry in a union has to be manually extended?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

3reactions
colinhackscommented, Mar 21, 2021

Okay, this issue, combination with #351, have convinced me that it’s worthwhile to keep ZodIntersection.

I’ve added it back and also added a and method to the base class as a way to easily define intersection types. If you change all your calls of A.merge(B) to A.and(B), your code should continue working as it did before the alpha.7 release. You’ll need to upgrade to the latest alpha to get the .and method: yarn add zod@next cc @jstewmon

Note that .merge will still allow key overwriting (like .extend).

0reactions
colinhackscommented, Mar 19, 2021

That works when Out is the same across all items in the tuple, but not if the type of an individual element in the output tuple varies based on the type of that element in the input.

Here’s my best attempt (doesn’t work)

const tmap = <T extends [AnyZodObject, ...AnyZodObject[]], Out>(
  a: T,
  fn: (item: T[number]) => Out
): { [k in keyof T]: Out } => {
  return a.map(fn) as { [k in keyof T]: Out };
};

const extension = { test: z.boolean() };

const asdf = tmap(
  [
    z.object({
      asdf: z.string(),
    }),
    z.object({
      asdf: z.number(),
    }),
  ],
  <T extends z.AnyZodObject>(
    asdf: T
  ): z.ZodObject<T["_shape"] & typeof extension> => {
    return asdf.extend({ test: z.boolean() }) as any;
  }
);

const aasdfsdf = asdf[0].shape;

You still end up with a union type for each element:

Screen Shot 2021-03-18 at 9 48 33 PM

Read more comments on GitHub >

github_iconTop Results From Across the Web

Chapter 4B. Traffic Control Signals—General - MUTCD
Determine the appropriate traffic control to be used after removal of the signal. Remove any sight-distance restrictions as necessary. Inform ...
Read more >
Best Practices for the Design and Operation of Reduced ...
Reduced Conflict Intersections │ Best Practices ... Snow Removal . ... The following guidance for the design of the left turn (J-turn) ...
Read more >
Intersection Design - CT.gov
The following sections describe characteristics of intersection users. ... general guidance for selecting design vehicles appropriate for intersection ...
Read more >
Removing Unwarranted Traffic Signals
Following removal of a traffic control signal, two-way stop control, all-way stop control or roundabout control may be used at the intersection. The...
Read more >
Traffic Signal Policy and Guidelines
the installation/removal of a traffic signal will improve the overall safety and operation of the intersection. The following elements ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found