Skip to content

Commit 39ea2c5

Browse files
authored
refactor(specs): split injection schema (#6224)
## 🧭 What and Why 🎟 Contributes to https://algolia.atlassian.net/browse/CMP-761 ### Stacked PR: * #6224 <- This PR * #6227 ### Changes included: #### → File reorg - **Split `Behaviour.yml`** into dedicated schema files: - **`InjectedItem.yml`** (new) for `injectionInjectedItem` schema - **`Main.yml`** (new) for `injectionMain` and `injectionMainSource` schemas - **`MainSource.yml`** (new) for `SearchSource` and `RecommendSource` (placeholder) for the main injection source `oneOf` - **Renamed schema files** for clarity: - `Source.yml` → `InjectedItemSource.yml` - `QueryParams.yml` → `SearchQueryParams.yml` #### → Strongly typed client impact generation - **Separated main and injected item source types**: `main.source and injectedItems[].source` now resolve to distinct `oneO`f types (`injectionMainSource` vs `injectedItemSource`), producing separate strongly-typed models in generated clients - **Added `RecommendSource` placeholder** to `injectionMainSource` `oneOf` to ensures the generator creates a proper `oneOf` wrapper type instead of inlining the single variant #### → A few comments Because all models are generated at the root level in the Go client (no sub-packages), two constraints arise: - **Every `title` must be unique across the entire package.** All generated struct names must be globally unique. In the context of the Composition API, it would have been more natural to use package separation allowing the same struct name (e.g., `SearchSource`) in different packages (`main` vs `injectedItem`). This would better reflect the intent of the API, where both sources share the same shape but serve different roles. - **Unique names lead to a significant increase in verbosity.** Each struct must encode its full context in its name to avoid collisions (e.g., `InjectedItemSourceSearchSource` instead of just `SearchSource`), making the generated code harder to read. - Key `title` does not behave the same way according to the language generated. From what I saw, it is for example completely ignored by the Go language for direct specs objects and at the same time require for inner object. According to what I read, in C# this handled for each value of `title` 🤷‍♀️ Additionally, since feature teams only modify the specs, **they cannot always anticipate that a future feature will require an object with a very similar shape.** This is forcing retroactive renames to maintain uniqueness, and therefore **producing breaking changes**. For example, the previous titles `compositionSourceSearch` or `compositionSource` no longer make sense now that both `main` and `injectedItems` have their own source types. _I am speaking for the Go client but because this one I understand the most but I am assuming this is the same thing for other strongly typed clients_ ## 🧪 Test * Run locally `yarn cli build specs all && yarn cli generate go composition` * CI
1 parent b41b8f4 commit 39ea2c5

7 files changed

Lines changed: 162 additions & 104 deletions

File tree

specs/composition/common/schemas/components/injection/Behaviour.yml

Lines changed: 5 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,108 +4,23 @@ injection:
44
additionalProperties: false
55
properties:
66
main:
7-
title: main
8-
type: object
9-
additionalProperties: false
10-
properties:
11-
source:
12-
title: compositionSource
13-
type: object
14-
additionalProperties: false
15-
properties:
16-
search:
17-
title: compositionSourceSearch
18-
type: object
19-
additionalProperties: false
20-
properties:
21-
index:
22-
type: string
23-
description: Composition Main Index name.
24-
example: Products
25-
params:
26-
$ref: './QueryParams.yml#/mainInjectionQueryParameters'
27-
required:
28-
- index
29-
required:
30-
- search
31-
required:
32-
- source
7+
$ref: './Main.yml#/injectionMain'
338
injectedItems:
349
type: array
3510
description: list of injected items of the current Composition.
3611
minItems: 0
37-
maxItems: 2
12+
maxItems: 3
3813
items:
39-
$ref: '#/injectedItem'
14+
$ref: './InjectedItem.yml#/injectionInjectedItem'
4015
deduplication:
4116
title: deduplication
4217
type: object
4318
additionalProperties: false
4419
description: Deduplication configures the methods used to resolve duplicate items between main search results and injected group results.
4520
properties:
4621
positioning:
47-
$ref: '#/dedupPositioning'
22+
$ref: './InjectedItem.yml#/dedupPositioning'
4823
required:
4924
- positioning
5025
required:
51-
- main
52-
53-
injectedItem:
54-
type: object
55-
additionalProperties: false
56-
properties:
57-
key:
58-
type: string
59-
description: injected Item unique identifier.
60-
source:
61-
description: Search source to be used to inject items into result set.
62-
$ref: '#/injectedItemSource'
63-
position:
64-
type: integer
65-
minimum: 0
66-
maximum: 19
67-
length:
68-
type: integer
69-
minimum: 0
70-
maximum: 20
71-
metadata:
72-
title: injectedItemMetadata
73-
type: object
74-
description: Used to add metadata to the results of the injectedItem.
75-
properties:
76-
hits:
77-
title: injectedItemHitsMetadata
78-
type: object
79-
description: Adds the provided metadata to each injected hit via an `_extra` attribute.
80-
properties:
81-
addItemKey:
82-
type: boolean
83-
description: When true, the `_injectedItemKey` field is set in the `_extra` object of each affected hit.
84-
extra:
85-
type: object
86-
additionalProperties: true
87-
description: The user-defined key-value pairs that will be placed in the `_extra` field of each affected hit.
88-
required:
89-
- key
90-
- source
91-
- position
92-
- length
93-
94-
injectedItemSource:
95-
oneOf:
96-
- $ref: './Source.yml#/SearchSource'
97-
- $ref: './Source.yml#/ExternalSource'
98-
99-
dedupPositioning:
100-
type: string
101-
enum:
102-
- highest
103-
- highestInjected
104-
description: |
105-
Deduplication positioning configures how a duplicate result should be resolved between an injected item and main search results.
106-
Current configuration supports:
107-
- 'highest': always select the item in the highest position, and remove duplicates that appear lower in the results.
108-
- 'highestInjected': duplicate result will be moved to its highest possible injected position, but not higher.
109-
If a duplicate appears higher in main search results, it will be removed to stay it's intended group position (which could be lower than main).
110-
example: highest
111-
default: highestInjected
26+
- main
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
injectionInjectedItem:
2+
type: object
3+
additionalProperties: false
4+
properties:
5+
key:
6+
type: string
7+
description: injected Item unique identifier.
8+
source:
9+
description: Source to be used to inject items into result set.
10+
$ref: '#/injectedItemSource'
11+
position:
12+
type: integer
13+
minimum: 0
14+
maximum: 99
15+
length:
16+
type: integer
17+
minimum: 0
18+
maximum: 50
19+
metadata:
20+
title: injectedItemMetadata
21+
type: object
22+
description: Used to add metadata to the results of the injectedItem.
23+
properties:
24+
hits:
25+
title: injectedItemHitsMetadata
26+
type: object
27+
description: Adds the provided metadata to each injected hit via an `_extra` attribute.
28+
properties:
29+
addItemKey:
30+
type: boolean
31+
description: When true, the `_injectedItemKey` field is set in the `_extra` object of each affected hit.
32+
extra:
33+
type: object
34+
additionalProperties: true
35+
description: The user-defined key-value pairs that will be placed in the `_extra` field of each affected hit.
36+
required:
37+
- key
38+
- source
39+
- position
40+
- length
41+
42+
injectedItemSource:
43+
oneOf:
44+
- $ref: './InjectedItemSource.yml#/injectedItemSearchSource'
45+
- $ref: './InjectedItemSource.yml#/injectedItemExternalSource'
46+
47+
dedupPositioning:
48+
type: string
49+
enum:
50+
- highest
51+
- highestInjected
52+
description: |
53+
Deduplication positioning configures how a duplicate result should be resolved between an injected item and main search results.
54+
Current configuration supports:
55+
- 'highest': always select the item in the highest position, and remove duplicates that appear lower in the results.
56+
- 'highestInjected': duplicate result will be moved to its highest possible injected position, but not higher.
57+
If a duplicate appears higher in main search results, it will be removed to stay it's intended group position (which could be lower than main).
58+
example: highest
59+
default: highestInjected

specs/composition/common/schemas/components/injection/Source.yml renamed to specs/composition/common/schemas/components/injection/InjectedItemSource.yml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
SearchSource:
2-
title: searchSource
1+
injectedItemSearchSource:
32
description: Injected items will originate from a search request performed on the specified index.
43
x-discriminator-fields:
54
- search
65
type: object
76
additionalProperties: false
87
properties:
98
search:
10-
title: search
9+
title: injectedItemSearch
1110
type: object
1211
additionalProperties: false
1312
properties:
@@ -16,22 +15,21 @@ SearchSource:
1615
description: Composition Index name.
1716
example: Products
1817
params:
19-
$ref: './QueryParams.yml#/injectedItemsQueryParameters'
18+
$ref: './SearchQueryParams.yml#/injectedItemsQueryParameters'
2019
required:
2120
- index
2221
required:
2322
- search
2423

25-
ExternalSource:
26-
title: externalSource
24+
injectedItemExternalSource:
2725
description: Injected items will originate from externally provided objectIDs (that must exist in the index) given at runtime in the run request payload.
2826
x-discriminator-fields:
2927
- external
3028
type: object
3129
additionalProperties: false
3230
properties:
3331
external:
34-
title: external
32+
title: injectedItemExternal
3533
type: object
3634
additionalProperties: false
3735
properties:
@@ -40,7 +38,7 @@ ExternalSource:
4038
description: Composition Index name.
4139
example: Products
4240
params:
43-
$ref: './QueryParams.yml#/injectedItemsQueryParameters'
41+
$ref: './SearchQueryParams.yml#/injectedItemsQueryParameters'
4442
ordering:
4543
$ref: '#/externalOrdering'
4644
required:
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
injectionMain:
2+
type: object
3+
additionalProperties: false
4+
description: Main defines the organic result set of the injection.
5+
properties:
6+
source:
7+
$ref: '#/injectionMainSource'
8+
9+
injectionMainSource:
10+
description: Source to be used to retrieve organic result set.
11+
oneOf:
12+
- $ref: './MainSource.yml#/injectionMainSearchSource'
13+
- $ref: './MainSource.yml#/injectionMainRecommendSource'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
injectionMainSearchSource:
2+
description: Organic result set will originate from a search request performed on the specified index.
3+
x-discriminator-fields:
4+
- search
5+
type: object
6+
additionalProperties: false
7+
properties:
8+
search:
9+
title: mainSearch
10+
type: object
11+
additionalProperties: false
12+
properties:
13+
index:
14+
type: string
15+
description: Targeted index name.
16+
example: Products
17+
params:
18+
$ref: './SearchQueryParams.yml#/mainInjectionQueryParameters'
19+
required:
20+
- index
21+
required:
22+
- search
23+
24+
injectionMainRecommendSource:
25+
description: Organic result set will originate from a recommend request.
26+
x-discriminator-fields:
27+
- recommend
28+
type: object
29+
additionalProperties: false
30+
properties:
31+
recommend:
32+
title: mainRecommend
33+
type: object
34+
additionalProperties: false
35+
properties:
36+
index:
37+
type: string
38+
description: Targeted index name.
39+
example: Products
40+
required:
41+
- index
42+
required:
43+
- recommend

specs/composition/common/schemas/components/injection/QueryParams.yml renamed to specs/composition/common/schemas/components/injection/SearchQueryParams.yml

File renamed without changes.

tests/output/go/tests/manual/composition_oneof_test.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ func TestInjectedItemSource_UnmarshalSearchSource(t *testing.T) {
1919
err := json.Unmarshal([]byte(input), &source)
2020
require.NoError(t, err)
2121

22-
require.NotNil(t, source.SearchSource, "expected SearchSource to be set")
23-
require.Nil(t, source.ExternalSource, "expected ExternalSource to be nil")
24-
require.Equal(t, "demo", source.SearchSource.Search.Index)
22+
require.NotNil(t, source.InjectedItemSearchSource, "expected SearchSource to be set")
23+
require.Nil(t, source.InjectedItemExternalSource, "expected ExternalSource to be nil")
24+
require.Equal(t, "demo", source.InjectedItemSearchSource.Search.Index)
2525
}
2626

2727
func TestInjectedItemSource_UnmarshalExternalSource(t *testing.T) {
@@ -34,9 +34,9 @@ func TestInjectedItemSource_UnmarshalExternalSource(t *testing.T) {
3434
err := json.Unmarshal([]byte(input), &source)
3535
require.NoError(t, err)
3636

37-
require.NotNil(t, source.ExternalSource, "expected ExternalSource to be set")
38-
require.Nil(t, source.SearchSource, "expected SearchSource to be nil")
39-
require.Equal(t, "sponsored", source.ExternalSource.External.Index)
37+
require.NotNil(t, source.InjectedItemExternalSource, "expected External Source to be set")
38+
require.Nil(t, source.InjectedItemSearchSource, "expected Search Source to be nil")
39+
require.Equal(t, "sponsored", source.InjectedItemExternalSource.External.Index)
4040
}
4141

4242
func TestInjectedItemSource_RoundTripSearchSource(t *testing.T) {
@@ -68,3 +68,33 @@ func TestInjectedItemSource_RoundTripExternalSource(t *testing.T) {
6868
require.NoError(t, err)
6969
require.JSONEq(t, input, string(output))
7070
}
71+
72+
func TestInjectionMainSource_UnmarshalSearchSource(t *testing.T) {
73+
t.Parallel()
74+
75+
input := `{"search": {"index": "demo"}}`
76+
77+
var source composition.InjectionMainSource
78+
79+
err := json.Unmarshal([]byte(input), &source)
80+
require.NoError(t, err)
81+
82+
require.NotNil(t, source.InjectionMainSearchSource, "expected SearchSource to be set")
83+
require.Nil(t, source.InjectionMainRecommendSource, "expected RecommendSource to be nil")
84+
require.Equal(t, "demo", source.InjectionMainSearchSource.Search.Index)
85+
}
86+
87+
func TestInjectionMainSource_RoundTripSearchSource(t *testing.T) {
88+
t.Parallel()
89+
90+
input := `{"search":{"index":"demo"}}`
91+
92+
var source composition.InjectionMainSource
93+
94+
err := json.Unmarshal([]byte(input), &source)
95+
require.NoError(t, err)
96+
97+
output, err := json.Marshal(&source)
98+
require.NoError(t, err)
99+
require.JSONEq(t, input, string(output))
100+
}

0 commit comments

Comments
 (0)