TypeScript type ignore case

后端 未结 4 1284
悲哀的现实
悲哀的现实 2021-02-12 22:42

I have this type definition in TypeScript:

export type xhrTypes = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"OPTIONS\" | \"CONNECT\" | \"HEAD\";
4条回答
  •  挽巷
    挽巷 (楼主)
    2021-02-12 23:00

    As @RyanCavanaugh said, TypeScript doesn't have case-insensitive string literals. [EDIT: I am reminded that there is an existing suggestion for TypeScript to support regexp-validated string literals, which would maybe allow for this, but it is not currently part of the language.]

    The only workaround I can think of is to enumerate the most likely variants of those literals (say, all lowercase, init cap) and make a function that can translate between them if needed:

    namespace XhrTypes {
      function m(
        t: T, ks: K[], v: V
      ): T & Record {
        (t as any)[v] = v;
        ks.forEach(k => (t as any)[k] = v);
        return t as any;
      }
      function id(t: T): { [K in keyof T]: T[K] } {
        return t;
      }
      const mapping = id(m(m(m(m(m(m(m({},
        ["get", "Get"], "GET"), ["post", "Post"], "POST"),
        ["put", "Put"], "PUT"), ["delete", "Delete"], "DELETE"),
        ["options", "Options"], "OPTIONS"), ["connect", "Connect"], "CONNECT"),
        ["head", "Head"], "HEAD"));      
    
      export type Insensitive = keyof typeof mapping
      type ForwardMapping = typeof mapping[I];
    
      export type Sensitive = ForwardMapping;     
      type ReverseMapping = 
        {[K in Insensitive]: ForwardMapping extends S ? K : never}[Insensitive];
    
      export function toSensitive(
        k: K ): ForwardMapping {
        return mapping[k];
      }
    
      export function matches(
        k: K, l: L ): k is K & ReverseMapping> {
        return toSensitive(k) === toSensitive(l);
      }
    }
    

    What ends up getting exported is the following types:

    type XhrTypes.Sensitive = "GET" | "POST" | "PUT" | "DELETE" | 
      "OPTIONS" | "CONNECT" | "HEAD"
    
    type XhrTypes.Insensitive = "get" | "Get" | "GET" | 
      "post" | "Post" | "POST" | "put" | "Put" | "PUT" | 
      "delete" | "Delete" | "DELETE" | "options" | "Options" |
      "OPTIONS" | "connect" | "Connect" | "CONNECT" | "head" | 
      "Head" | "HEAD"
    

    and the functions

     function XhrTypes.toSensitive(k: XhrTypes.Insensitive): XhrTypes.Sensitive;
    
     function XhrTypes.matches(k: XhrTypes.Insensitive, l: XhrTypes.Insensitive): boolean;
    

    I'm not sure what you (@Knu) need this for or how you plan to use it, but I'm imagining that you want to convert between sensitive/insensitive methods, or check to see if two case-insensitive methods are a match. Obviously you can do those at runtime by just converting to uppercase or doing a case-insensitive compare, but at compile time the above types may be useful.

    Here's an example of using it:

    interface HttpStuff {
      url: string,
      method: XhrTypes.Insensitive,
      body?: any
    }
    const httpStuff: HttpStuff = {
      url: "https://google.com",
      method: "get"
    }
    
    interface StrictHttpStuff {
      url: string,
      method: XhrTypes.Sensitive,
      body?: any
    }
    declare function needStrictHttpStuff(httpStuff: StrictHttpStuff): Promise<{}>;
    
    needStrictHttpStuff(httpStuff); // error, bad method
    
    needStrictHttpStuff({
       url: httpStuff.url, 
       method: XhrTypes.toSensitive(httpStuff.method) 
      }); // okay
    

    In the above, there's a function that expects uppercase values, but you can safely pass it a case insensitive value if you use XhrTypes.toSensitive() first, and the compiler verifies that "get" is an acceptable variant of "GET" in this case.

    Okay, hope that helps. Good luck.

提交回复
热议问题