Repository navigation
Conversation
|
Ok, we can add this if we also ensure that codegen fails if Can you add that as well @noelgbl? |
|
Good catch, Done! |
Torkan
left a comment
There was a problem hiding this comment.
Thanks for adding that so quickly @noelgbl! 🙏
While testing it I realised my earlier suggestion was too broad, sorry about that. "Constraints" covers far more than I meant. The only real conflict is a fields constraint: that's the one case where map_typed_container/2 builds the typed map and quietly ignores typescript_type_name/0. Other constraints (min/max, max_length, defaults declared in a custom type's constraints/0) don't change the TS shape. Rejecting them breaks setups that work on main today.
Inline I've put the cases I hit, a suggestion for narrowing the check, and a small thing about the error message.
| end | ||
|
|
||
| @doc false | ||
| def custom_type_with_constraints?(%Type{module: module, constraints: constraints}) do |
There was a problem hiding this comment.
This does what I asked for, but in practice it catches too much. On this branch both of these now fail codegen, and main accepts them:
# Constrained scalar NewType. On main it maps to "CustomTypes.Percentage".
use Ash.Type.NewType, subtype_of: :float, constraints: [min: 0.0, max: 100.0]
def typescript_type_name, do: "CustomTypes.Percentage"# `use Ash.Type` with a defaulted constraint. Flagged even when the attribute
# passes no constraints, because Ash fills in the default.
def constraints, do: [precision: [type: :integer, default: 2]]
def typescript_type_name, do: "string"The result also depends on where the constraints are set. For NewTypes the usage-site constraints are dropped, so attribute :x, PlainPct, constraints: [min: 0.0] passes, while the same constraints inside the NewType's use fail.
Could we only flag when the effective constraints contain fields? That's exactly when the name gets dropped. Roughly:
def custom_type_with_constraints?(%Type{module: module} = type) do
is_atom(module) and Introspection.is_custom_type?(module) and Type.get_fields(type) != []
end(Or a Keyword.has_key?(..., :fields) check on the folded constraints, whichever fits better.) A name like custom_type_with_fields?/1 would then describe it better.
| @@ -133,16 +146,20 @@ defmodule AshTypescript.Manifest.Verifiers.VerifyMappableTypes do | |||
| defp build_error(offenders, module) do | |||
There was a problem hiding this comment.
Small one: the per-type conflict line is great, but the surrounding header ("Unsupported types found…") and footer ("implement the typescript_type_name/0 callback…") are always added. So when the only offender is a conflict, the footer tells the user to add the callback that's causing it 😅
Rather than splitting by reason, maybe a generic header/footer that covers both cases would work, e.g.:
Some types cannot be mapped to TypeScript:
...
Each custom type needs exactly one way to map to TypeScript. Either:
- implement the `typescript_type_name/0` callback on the type module, or
- add an entry to `config :ash_typescript, type_mapping_overrides: [{TheModule, "<ts type>"}]`, or
- (map types) declare `fields` constraints so AshTypescript generates a typed map
These are mutually exclusive: a type with `fields` constraints cannot also use
`typescript_type_name/0`. Remove one or the other.
The per-type line could then say "defines both typescript_type_name/0 and fields constraints", so it's clear which entries are conflicts and which are missing mappings. (This assumes the check is narrowed to fields as in the other comment.)
| end | ||
| end | ||
|
|
||
| describe "custom type conflicts" do |
There was a problem hiding this comment.
Could you add a few cases that pin down the boundary? None of the current fixtures has constraints, which is why the suite stayed green:
- scalar NewType with
constraints: [min:, max:]+typescript_type_name/0→ accepted use Ash.Typewith a defaulted entry inconstraints/0+typescript_type_name/0→ accepted- map NewType with
fields+typescript_type_name/0→ rejected (your existing test), plus an assertion that the message uses the new mutually-exclusive wording rather than only "implementtypescript_type_name/0"
|
@Torkan thanks for review. Hope I got everything covered. |
A NewType of
:mapwithoutfieldsconstraints ignoredtypescript_type_name/0and was generated as the untyped map type (Record<string, any>). Scalar NewTypes already use the callback (seePercentage), so map NewTypes were the only ones where it had no effect.Change
TypeMapper.map_typed_container/2, a container without fields now goes throughunknown_module_mapping/1, the same lookup scalar NewTypes use, before falling back tountyped_map_type.fieldsstill generate their typed map, which field selection depends on.Tests
Todo.Layoutfixture (a map NewType), added as thelayoutattribute onTodo.custom_types_test.exs: the type on its own, inside an array, and in the full generated output.test/ts/shouldPass/customTypes.ts:layoutis typed asCustomTypes.Layout. This check fails on the old codegen.documentation/advanced/custom-types.md.Contributor checklist