pmcelhaney/counterfact

[BUG] Recursive models generate invalid typescript

kaikun213 opened this issue · 3 comments

We have the following python model:

class FilterRule(BaseModel):
    value: str | list[str]
    operator: Literal["eq", "ne", "in"]
    field: str
    field_type: Literal["list", "string"] = "string"

    def __str__(self) -> str:
        if self.field_type == "list":
            if isinstance(self.value, list):
                joined_value = ", ".join([v for v in self.value])
            else:
                joined_value = self.value
            return f"{self.field}/any(g: search.in(g, '{joined_value}'))"
        else:
            if self.operator == "in":
                if isinstance(self.value, list):
                    predicate = " or ".join(
                        [f"{self.field} eq '{v}' " for v in self.value]
                    )
                else:
                    predicate = f"{self.field} eq '{self.value}'"
                return f"({predicate})" if predicate else ""

        return f"{self.field} {self.operator} '{self.value}'"

    def __bool__(self) -> bool:
        return bool(self.value) and bool(self.field)


class Filters(BaseModel):
    logical_op: Literal["and", "or", "not"] = "and"
    rules: list[Union[FilterRule, "Filters"]]

    def __str__(self) -> str:
        filter_str = f" {self.logical_op} ".join(
            [
                str(rule) if isinstance(rule, FilterRule) else f"({rule})"
                for rule in self.rules
                if bool(rule)
            ]
        )

        return filter_str.strip()

Which generates the following OpenAPI schema type:

"Filters":{
   "properties":{
      "logical_op":{
         "type":"string",
         "enum":[
            "and",
            "or",
            "not"
         ],
         "title":"Logical Op",
         "default":"and"
      },
      "rules":{
         "items":{
            "oneOf":[
               {
                  "$ref":"#/components/schemas/FilterRule"
               },
               {
                  "$ref":"#/components/schemas/Filters"
               }
            ]
         },
         "type":"array",
         "title":"Rules"
      }
   },
   "type":"object",
   "required":[
      "rules"
   ],
   "title":"Filters"
},

Which is correct. But the generated typescript file is invalid:

import type { FilterRule } from "./FilterRule.js";
import type { Filters2 } from "./Filters.js";

export type Filters = {
  logical_op?: "and" | "or" | "not";
  rules: Array<FilterRule | Filters2>;
};

There exists not "Filters2". It should simply be:

import type { FilterRule } from "./FilterRule.js";

export type Filters = {
  logical_op?: "and" | "or" | "not";
  rules: Array<FilterRule | Filters>;
};

How can this be fixed? If we manually adjust it, every time we run the client it is overwritten.

Thanks for letting me know about this! I will look at this and get it fixed ASAP.

Unfortunately, there's not yet a way to exclude a type file from being automatically recreated. That's coming soon (#886). You've also inspired me add another feature to prevent an individual file from being overwritten (#891). That will be trivial to implement so worst case I'll have a release out by the end of the day that allows you to keep your manual fix.

It's probably a matter of removing the !this.exports.has(name) part in this line.

if (!this.imports.has(name) && !this.exports.has(name)) {

Fixed in 0.40.2. Thank you for the bug report! If you have any other feedback, please don't hesitate to share.