feat: expose composite foreign keys and m2m columns in OpenAPI

Replace the single-column `<fk table='...' column='...'/>` marker with a
unified, composite-capable format `<fk table='...' columns='local:foreign,...'/>`
and emit full column lists for many-to-many markers, so clients can
reconstruct composite relationships.

- Match every real table relationship a column participates in, instead of
  only single-column foreign keys, still preferring tables over the
  view-derived relationships.
- Emit the full ordered local->foreign column mapping in the `<fk>` marker
  and the full source/target column lists in the `<m2m>` marker.
- Add composite foreign key and composite m2m fixtures plus OpenAPI spec
  assertions.
This commit is contained in:
2026-09-04 23:53:23 +02:00
parent 7989108b0b
commit 72b9a116be
3 changed files with 120 additions and 20 deletions
+44 -5
View File
@@ -165,7 +165,7 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
"type": "string"
},
"parent_id": {
"description": "Note:\nThis is a Foreign Key to `entities.id`.<fk table='entities' column='id'/>",
"description": "Note:\nThis is a Foreign Key to `entities.id`.<fk table='entities' columns='parent_id:id'/>",
"format": "int32",
"type": "integer"
}
@@ -200,7 +200,7 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
"type": "string"
},
"parent_id": {
"description": "Note:\nThis is a Foreign Key to `entities.id`.<fk table='entities' column='id'/>",
"description": "Note:\nThis is a Foreign Key to `entities.id`.<fk table='entities' columns='parent_id:id'/>",
"format": "int32",
"type": "integer"
}
@@ -285,7 +285,7 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Unique column.<unique/>\nThis is a Foreign Key to `second.id`.<fk table='second' column='id'/>"
"description": "Note:\nThis is a Unique column.<unique/>\nThis is a Foreign Key to `second.id`.<fk table='second' columns='second_id_1:id'/>"
}
|]
@@ -370,6 +370,45 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
beingDescription `shouldBe` Just
[aesonQQ|"<m2m table='part' junction='being_part' source='being' target='part'/>"|]
it "includes a composite foreign key marker with the full column mapping" $ do
r <- simpleBody <$> get "/"
let parentFk = r ^? key "definitions" . key "comp_component_instance" . key "properties" . key "product_instance_id"
liftIO $
parentFk `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Primary Key.<pk/>\nThis is a Foreign Key to `comp_product_instance.(id, product_id)`.<fk table='comp_product_instance' columns='product_instance_id:id,product_id:product_id'/>"
}
|]
it "includes a single column foreign key marker with the unified format" $ do
r <- simpleBody <$> get "/"
let productFk = r ^? key "definitions" . key "comp_product_instance" . key "properties" . key "product_id"
liftIO $
productFk `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is part of a composite unique constraint.<unique cols='id,product_id'/>\nThis is a Foreign Key to `comp_product.id`.<fk table='comp_product' columns='product_id:id'/>"
}
|]
it "includes composite m2m relationship markers in the table description" $ do
r <- simpleBody <$> get "/"
let beingDescription = r ^? key "definitions" . key "comp_being" . key "description"
liftIO $
beingDescription `shouldBe` Just
[aesonQQ|"<m2m table='comp_thing' junction='comp_being_thing' source='a,b' target='x,y'/>"|]
describe "Foreign table" $
it "includes foreign table properties" $ do
@@ -471,7 +510,7 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Foreign Key to `pages.link`.<fk table='pages' column='link'/>"
"description": "Note:\nThis is a Foreign Key to `pages.link`.<fk table='pages' columns='link:link'/>"
}
|]
@@ -488,7 +527,7 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Foreign Key to `clients.id`.<fk table='clients' column='id'/>"
"description": "Note:\nThis is a Foreign Key to `clients.id`.<fk table='clients' columns='client_id:id'/>"
}
|]