Skip to content

Commit 5f574d7

Browse files
authored
Recipe marketplace CSV (#6150)
1 parent 17ad316 commit 5f574d7

37 files changed

+3389
-77
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# 6. Recipe Marketplace CSV Format
2+
3+
Date: 2025-01-27
4+
5+
## Status
6+
7+
Accepted
8+
9+
## Context
10+
11+
We need a standardized way to represent and exchange recipe marketplace data, including recipes, their categorization, options, and bundle information. The format should:
12+
13+
1. Be human-readable and editable
14+
2. Support hierarchical categorization of recipes
15+
3. Accommodate recipes with varying numbers of options
16+
4. Handle both minimal recipe catalogs (descriptions only) and enriched marketplaces (with bundle installation details)
17+
5. Support round-trip serialization/deserialization without data loss
18+
19+
Similar to the approach used in `moderne-organizations-format` for repository organization, we chose CSV as the interchange format due to its widespread tool support, simplicity, and ease of integration with existing systems.
20+
21+
## Decision
22+
23+
We will use a CSV format for recipe marketplace data with the following structure:
24+
25+
### Required Columns
26+
27+
- **`name`**: The fully qualified recipe name (e.g., `org.openrewrite.java.cleanup.UnnecessaryParentheses`)
28+
- **`category1, category2, ..., categoryN`**: Zero or more category columns, read **left to right** with **left representing the deepest level category**
29+
30+
### Optional Recipe Columns
31+
32+
- **`displayName`**: Human-readable recipe display name
33+
- **`description`**: Recipe description
34+
- **`option1Name, option1DisplayName, option1Description`**: First recipe option
35+
- **`option2Name, option2DisplayName, option2Description`**: Second recipe option
36+
- **`optionNName, optionNDisplayName, optionNDescription`**: Additional options following the same pattern
37+
38+
### Optional Bundle Columns
39+
40+
Bundle columns describe where a recipe can be installed from. When absent, the marketplace represents a minimal catalog:
41+
42+
- **`ecosystem`**: Package ecosystem (e.g., `Maven`, `npm`, `yaml`)
43+
- **`packageName`**: Package identifier (e.g., `org.openrewrite:rewrite-java`, npm package name)
44+
- **`version`**: Package version
45+
- **`team`**: Optional team identifier for marketplace partitioning
46+
47+
### Category Structure
48+
49+
The `RecipeMarketplace` is a recursive data structure where each category is itself a marketplace. Categories are determined by column headers starting with "category":
50+
51+
```csv
52+
name,category1,category2,category3
53+
org.openrewrite.java.cleanup.UnnecessaryParentheses,Cleanup,Java,Best Practices
54+
```
55+
56+
This creates the hierarchy: `Best Practices > Java > Cleanup > UnnecessaryParentheses`
57+
58+
The displayName of a category corresponds to the value in its category column.
59+
60+
### Epsilon Root
61+
62+
When a CSV contains multiple top-level categories, a synthetic "epsilon root" (`ε`) is created similar to `moderne-organizations-format`. This root:
63+
64+
- Uses the epsilon character (`\u03B5`) as its display name
65+
- Is identified via `RecipeMarketplace.isRoot()`
66+
- Is never written to CSV output
67+
- Allows the reader to return a single root when multiple disparate category trees exist
68+
69+
### Minimal vs. Enriched Marketplaces
70+
71+
**Minimal Marketplace**: Contains only recipe metadata (name, categories, options) without bundle information. Useful for describing what recipes exist and their categorization.
72+
73+
**Enriched Marketplace**: Includes bundle columns (ecosystem, packageName, version, team). Created when recipes are "installed" into an environment, combining the minimal catalog with actual bundle provenance.
74+
75+
### Implementation
76+
77+
- **Reader**: `RecipeMarketplaceReader` (using univocity-parsers)
78+
- Parses CSV into `RecipeMarketplace` hierarchies
79+
- Accepts optional `RecipeBundleLoader` instances via constructor
80+
- Creates bundle instances via loaders when ecosystem, packageName, and version are present
81+
- Creates `RecipeOffering` instances with null bundles when bundle columns are absent or no loader is configured
82+
- Returns epsilon root when multiple top-level categories exist
83+
84+
- **Writer**: `RecipeMarketplaceWriter` (using univocity-parsers)
85+
- Dynamically determines required category and option columns
86+
- Filters epsilon root from output
87+
- Only includes bundle columns if at least one recipe has bundle information
88+
89+
- **Bundle Loaders**: Configurable implementations passed to the reader
90+
- `MavenRecipeBundleLoader` in `rewrite-maven`: Creates `MavenRecipeBundle` instances, requires `MavenExecutionContextView` and `MavenArtifactDownloader`
91+
- `NpmRecipeBundleLoader` in `rewrite-javascript`: Creates `NpmRecipeBundle` instances
92+
- `RecipeBundleLoader` interface allows additional ecosystems to be added without modifying core
93+
94+
## Consequences
95+
96+
### Positive
97+
98+
1. **Human-editable**: CSV files can be created and modified in spreadsheet tools or text editors
99+
2. **Flexible schema**: Dynamic column detection accommodates varying numbers of categories and options
100+
3. **Separation of concerns**: Minimal marketplaces can describe recipes independently of their installation source
101+
4. **Composable**: Multiple marketplace CSVs can be combined by merging rows
102+
5. **Round-trip compatible**: Reader and writer preserve all information
103+
6. **Familiar pattern**: Mirrors `moderne-organizations-format` conventions, reducing learning curve
104+
7. **Extensible bundle loading**: RecipeBundleLoader interface allows new package ecosystems to be added without modifying core modules
105+
106+
### Negative
107+
108+
1. **CSV limitations**: No native support for nested structures (mitigated by column naming conventions)
109+
2. **Sparse data**: Recipes with few options result in many empty cells in CSVs with high option counts
110+
3. **Manual maintenance**: Keeping bundle information synchronized with actual artifact versions requires tooling
111+
4. **No validation**: CSV format doesn't enforce recipe name uniqueness or category integrity
112+
113+
### Trade-offs
114+
115+
- **Left-to-right category ordering** (left = deepest): This matches the `moderne-organizations-format` convention but may be counterintuitive to some users who expect left-to-right to represent root-to-leaf
116+
- **Null bundles for minimal marketplaces**: Simplifies the model but means `RecipeOffering.describe()` and `prepare()` throw exceptions until bundles are associated
117+
- **Dynamic columns**: Provides flexibility but means schema varies between files, making generic CSV processing tools less effective
118+
- **Bundle loader configuration**: Bundle loaders require runtime dependencies (e.g., MavenExecutionContextView) that must be provided when constructing the loader and passed to RecipeMarketplaceReader constructor
119+
120+
## Examples
121+
122+
### Minimal Marketplace
123+
124+
```csv
125+
name,displayName,description,category
126+
org.openrewrite.java.cleanup.UnnecessaryParentheses,Remove Unnecessary Parentheses,Removes unnecessary parentheses,Java Cleanup
127+
```
128+
129+
### With Options
130+
131+
```csv
132+
name,displayName,option1Name,option1DisplayName,option1Description,option2Name,option2DisplayName,option2Description,category
133+
org.openrewrite.maven.UpgradeDependencyVersion,Upgrade Dependency,groupId,Group ID,The group ID,artifactId,Artifact ID,The artifact ID,Maven
134+
```
135+
136+
### Enriched with Bundle Information
137+
138+
```csv
139+
name,displayName,category,ecosystem,packageName,version,team
140+
org.openrewrite.java.cleanup.UnnecessaryParentheses,Remove Unnecessary Parentheses,Java Cleanup,Maven,org.openrewrite:rewrite-java,8.0.0,java-team
141+
```
142+
143+
### Multi-level Categories
144+
145+
```csv
146+
name,category1,category2,category3
147+
org.openrewrite.java.cleanup.UnnecessaryParentheses,Cleanup,Java,Best Practices
148+
org.openrewrite.java.format.AutoFormat,Formatting,Java,Best Practices
149+
```
150+
151+
Creates: `Best Practices > Java > Cleanup > UnnecessaryParentheses` and `Best Practices > Java > Formatting > AutoFormat`

0 commit comments

Comments
 (0)