From a06d02648b60ae16ad69369f67405ca283d6e9d3 Mon Sep 17 00:00:00 2001 From: mattkhan <86168986+mattkhan@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:09:44 -0700 Subject: [PATCH] feat: infer AR enums as unions config --- lib/anchor/config.rb | 4 +++- lib/anchor/resource.rb | 5 ++++- spec/example/app/models/exhaustive.rb | 2 ++ spec/example/config/initializers/anchor.rb | 1 + spec/example/test/files/all_fields_false_schema.ts | 2 +- spec/example/test/files/excluded_fields_schema.ts | 2 +- spec/example/test/files/json_schema.json | 2 +- spec/example/test/files/multifile/Exhaustive.ts | 2 +- spec/example/test/files/schema.ts | 2 +- spec/example/test/files/test_schema.ts | 2 +- 10 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/anchor/config.rb b/lib/anchor/config.rb index ab459cb..236cb59 100644 --- a/lib/anchor/config.rb +++ b/lib/anchor/config.rb @@ -10,7 +10,8 @@ class Config :maybe_as_union, :array_bracket_notation, :infer_default_as_non_null, - :ar_comment_to_string + :ar_comment_to_string, + :infer_ar_enums def initialize @ar_column_to_type = nil @@ -24,6 +25,7 @@ def initialize @array_bracket_notation = nil @infer_default_as_non_null = nil @ar_comment_to_string = nil + @infer_ar_enums = nil end end end diff --git a/lib/anchor/resource.rb b/lib/anchor/resource.rb index 925c324..8ff6831 100644 --- a/lib/anchor/resource.rb +++ b/lib/anchor/resource.rb @@ -76,8 +76,11 @@ def anchor_attributes_properties(included_fields:) serializer_defined = (_model_class.try(:attribute_types) || {})[model_method.to_s].respond_to?(:coder) method_defined = model_method_defined || resource_method_defined || serializer_defined + enum = Anchor.config.infer_ar_enums && !method_defined && _model_class.try(:defined_enums).try(:[], model_method.to_s) column = !method_defined && _model_class.try(:columns_hash).try(:[], model_method.to_s) - if column + if enum + Anchor::Types::Union.new(enum.map { |_key, val| Anchor::Types::Literal.new(val) }) + elsif column type = Anchor::Types::Inference::ActiveRecord::SQL.from(column) unless description description = column.comment if Anchor.config.use_active_record_comment diff --git a/spec/example/app/models/exhaustive.rb b/spec/example/app/models/exhaustive.rb index 73a1989..6ded58b 100644 --- a/spec/example/app/models/exhaustive.rb +++ b/spec/example/app/models/exhaustive.rb @@ -2,4 +2,6 @@ class Exhaustive < ApplicationRecord validates :maybe_string, presence: true def model_overridden = "model_overridden" + + enum :enum, { enum_sample: "sample", enum_enum: "enum", enum_value: "value" } end diff --git a/spec/example/config/initializers/anchor.rb b/spec/example/config/initializers/anchor.rb index 68f20ac..158878d 100644 --- a/spec/example/config/initializers/anchor.rb +++ b/spec/example/config/initializers/anchor.rb @@ -5,6 +5,7 @@ module Anchor c.use_active_record_validations = true c.infer_default_as_non_null = true c.infer_nullable_relationships_as_optional = true + c.infer_ar_enums = true c.ar_column_to_type = lambda { |column| return Types::Literal.new("never") if column.name == "loljk" diff --git a/spec/example/test/files/all_fields_false_schema.ts b/spec/example/test/files/all_fields_false_schema.ts index 575753e..49e61ae 100644 --- a/spec/example/test/files/all_fields_false_schema.ts +++ b/spec/example/test/files/all_fields_false_schema.ts @@ -81,7 +81,7 @@ export type Exhaustive = { json: Record; jsonb: Record; daterange: unknown; - enum: unknown; + enum: "sample" | "enum" | "value"; virtualUpcasedString: Maybe; loljk: "never"; delegatedMaybeString: string; diff --git a/spec/example/test/files/excluded_fields_schema.ts b/spec/example/test/files/excluded_fields_schema.ts index 56626a6..bca9cd2 100644 --- a/spec/example/test/files/excluded_fields_schema.ts +++ b/spec/example/test/files/excluded_fields_schema.ts @@ -79,7 +79,7 @@ export type Exhaustive = { json: Record; jsonb: Record; daterange: unknown; - enum: unknown; + enum: "sample" | "enum" | "value"; virtualUpcasedString: Maybe; loljk: "never"; delegatedMaybeString: string; diff --git a/spec/example/test/files/json_schema.json b/spec/example/test/files/json_schema.json index 7c16d48..66fc780 100644 --- a/spec/example/test/files/json_schema.json +++ b/spec/example/test/files/json_schema.json @@ -1 +1 @@ -{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"withParsedComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","withParsedComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{"oneOf":[{"enum":["sample"]},{"enum":["enum"]},{"enum":["value"]}]},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"withParsedComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","withParsedComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file diff --git a/spec/example/test/files/multifile/Exhaustive.ts b/spec/example/test/files/multifile/Exhaustive.ts index dc48bde..a09e5dd 100644 --- a/spec/example/test/files/multifile/Exhaustive.ts +++ b/spec/example/test/files/multifile/Exhaustive.ts @@ -46,7 +46,7 @@ type Model = { json: Record; jsonb: Record; daterange: unknown; - enum: unknown; + enum: "sample" | "enum" | "value"; virtualUpcasedString: Maybe; loljk: "never"; delegatedMaybeString: string; diff --git a/spec/example/test/files/schema.ts b/spec/example/test/files/schema.ts index ae16658..8621ad1 100644 --- a/spec/example/test/files/schema.ts +++ b/spec/example/test/files/schema.ts @@ -88,7 +88,7 @@ export type Exhaustive = { json: Record; jsonb: Record; daterange: unknown; - enum: unknown; + enum: "sample" | "enum" | "value"; virtualUpcasedString: Maybe; loljk: "never"; delegatedMaybeString: string; diff --git a/spec/example/test/files/test_schema.ts b/spec/example/test/files/test_schema.ts index b228c01..ea77270 100644 --- a/spec/example/test/files/test_schema.ts +++ b/spec/example/test/files/test_schema.ts @@ -85,7 +85,7 @@ export type Exhaustive = { json: Record; jsonb: Record; daterange: unknown; - enum: unknown; + enum: "sample" | "enum" | "value"; virtualUpcasedString: Maybe; loljk: "never"; delegatedMaybeString: string;