Skip to content

Commit c18eda9

Browse files
feat: enhance configuration and documentation for Fabrica project
- Updated FabricaConfig structure to include detailed comments on configuration options. - Deprecated versioning configuration in FabricaConfig; moved to apis.yaml. - Added readAPIsConfig function to load apis.yaml for versioning. - Modified generate command to support resource discovery from apis.yaml. - Improved init command to create apis.yaml alongside .fabrica.yaml during project initialization. - Added comprehensive documentation for apis.yaml structure and usage. - Updated versioning command to work with the new apis.yaml format. - Adjusted generator defaults to disable versioning by default. Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
1 parent c646ad1 commit c18eda9

File tree

12 files changed

+695
-208
lines changed

12 files changed

+695
-208
lines changed

.github/workflows/regression-tests.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
steps:
2828
- name: Checkout code
29-
uses: actions/checkout@v5
29+
uses: actions/checkout@v6
3030

3131
- name: Set up Go
3232
uses: actions/setup-go@v6
@@ -38,8 +38,6 @@ jobs:
3838
run: make build
3939

4040
- name: Run Go integration tests
41-
env:
42-
GOPRIVATE: "github.com/me/*,example.com/*"
4341
run: |
4442
cd test/integration
4543
go mod tidy

README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ SPDX-License-Identifier: MIT
1919
> **🏗️ Code Generator for Go REST APIs**
2020
> Transform Go structs into production-ready REST APIs with OpenAPI specs, storage backends, and middleware in minutes.
2121
22+
## 📚 Documentation & Resources
23+
24+
| Resource | Description |
25+
|----------|-------------|
26+
| **[Full Documentation](https://openchami.github.io/fabrica/)** | Complete guides, tutorials, and best practices |
27+
| **[API Reference (GoDoc)](https://pkg.go.dev/github.com/openchami/fabrica)** | Comprehensive Go package documentation |
28+
| **[Getting Started Guide](docs/guides/getting-started.md)** | Step-by-step introduction to Fabrica |
29+
| **[Examples](examples/)** | Hands-on learning with real-world projects |
30+
2231
Fabrica is a powerful code generation tool that accelerates API development by transforming simple Go struct definitions into complete, production-ready REST APIs. Define your resources once, and Fabrica generates everything you need: handlers, storage layers, clients, validation, OpenAPI documentation, and more.
2332

2433
## ✨ Key Features
@@ -117,7 +126,7 @@ type DeviceStatus struct {
117126
fabrica generate
118127
```
119128

120-
This generates handlers, storage, and client code. By default, APIs expose `<group>/v1`. To add multiple versions (`v1alpha1`, `v1beta1`, etc.), create an `apis.yaml` file (see [Hub/Spoke Versioning Guide](docs/versioning.md)).
129+
This generates handlers, storage, and client code. By default, APIs expose `<group>/v1`. Your project includes a root-level `apis.yaml` file defining API groups and versions (see [Hub/Spoke Versioning Guide](docs/versioning.md)). To add more versions, run `fabrica add version <new-version>`.
121130

122131
**5. Update dependencies:**
123132

@@ -304,25 +313,32 @@ type DeviceStatus struct {
304313
> **💡 Pro Tip:** Focus on designing your `spec` and `status` structs - Fabrica handles all the envelope complexity automatically!
305314
306315

307-
## 📖 Documentation
316+
## 📖 In-Depth Documentation
308317

309-
**🚀 Getting Started:**
318+
**🚀 Quick Learning:**
310319
- [Complete Getting Started Guide](docs/guides/getting-started.md) - Step-by-step tutorial
311-
- [Quick Start Examples](examples/) - Hands-on learning
320+
- [Quickstart Examples](examples/) - Hands-on learning with working code
321+
- [Full Documentation Website](https://openchami.github.io/fabrica/) - All guides and tutorials
312322

313323
**🏗️ Architecture & Design:**
314324
- [Architecture Overview](docs/reference/architecture.md) - Understanding Fabrica's design principles
315325
- [Resource Model Guide](docs/guides/resource-model.md) - How to design and define resources
326+
- [API Versioning Guide](docs/guides/versioning.md) - Hub/Spoke versioning patterns
327+
- [API Configuration Reference](docs/apis-yaml.md) - apis.yaml structure and workflows
316328

317329
**💾 Storage & Data:**
318330
- [Storage Systems](docs/guides/storage.md) - File vs database backends comparison
319331
- [Ent Storage Integration](docs/guides/storage-ent.md) - Database setup and configuration
320332

321333
**⚙️ Advanced Topics:**
322-
- [Code Generation](docs/reference/codegen.md) - How templates work and customization
334+
- [Code Generation Reference](docs/reference/codegen.md) - How templates work and customization
323335
- [Validation System](docs/guides/validation.md) - Request validation and error handling
324-
- [Event System](docs/guides/events.md) - CloudEvents integration
336+
- [Event System](docs/guides/events.md) - CloudEvents integration and event-driven patterns
325337
- [Reconciliation](docs/guides/reconciliation.md) - Controller pattern for resource management
338+
- [Conditional Requests & PATCH](docs/guides/conditional-and-patch.md) - ETag-based preconditions
339+
340+
**📖 API Documentation:**
341+
- [GoDoc Package Reference](https://pkg.go.dev/github.com/openchami/fabrica) - Complete Go API documentation
326342

327343
## 🤝 Contributing
328344

USAGE.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -224,25 +224,25 @@ open http://localhost:8080/swagger/
224224
## Common Workflows
225225

226226
### 1. Add API Versioning (Hub/Spoke Pattern)
227+
228+
The root-level `apis.yaml` is created by `fabrica init` and drives version management.
229+
227230
```bash
228-
# Create apis.yaml in project root
229-
cat > apis.yaml << 'EOF'
230-
groups:
231-
- name: api.example
232-
versions:
233-
- v1 # Storage/hub version (v1)
234-
- v1beta1 # Conversion to v1beta1
235-
- v1alpha1 # Conversion to v1alpha1
236-
EOF
231+
# Add a new version (copies types from latest version)
232+
fabrica add version v1beta1
237233

238-
# Regenerate
234+
# Or copy from a specific version
235+
fabrica add version v1beta1 --from v1alpha1
236+
237+
# apis.yaml is updated automatically; regenerate to create handlers
239238
fabrica generate
240239
```
241240

242241
Now your API supports:
243-
- `api.example/v1` - Current stable (storage version)
244-
- `api.example/v1beta1` - Experimental
245-
- `api.example/v1alpha1` - Alpha
242+
- `infra.example.io/v1` - Storage (hub) version
243+
- `infra.example.io/v1beta1` - New spoke version
244+
245+
For details, see [docs/apis-yaml.md](docs/apis-yaml.md) and [docs/versioning.md](docs/versioning.md).
246246

247247
### 2. Switch from File to Database Storage
248248
```bash

cmd/fabrica/add.go

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -109,55 +109,46 @@ func runAddResource(resourceName string, opts *addOptions) error {
109109
return fmt.Errorf("failed to load config: %w", err)
110110
}
111111

112-
// Determine target version and directory
113-
var targetDir string
114-
var isVersioned bool
115-
116-
if config.Features.Versioning.Enabled && len(config.Features.Versioning.Versions) > 0 {
117-
isVersioned = true
118-
119-
// Require group to be set for versioned projects
120-
if config.Features.Versioning.Group == "" {
121-
return fmt.Errorf("versioning is enabled but features.versioning.group is not set in .fabrica.yaml.\nPlease set the API group (e.g., 'infra.example.io') in your configuration")
112+
apisConfig, err := LoadAPIsConfig("")
113+
if err != nil {
114+
if os.IsNotExist(err) {
115+
return fmt.Errorf("apis.yaml not found; run 'fabrica init' to create it")
122116
}
117+
return fmt.Errorf("failed to load apis.yaml: %w", err)
118+
}
123119

124-
// Version is required for versioned projects
125-
if opts.version == "" {
126-
// Auto-select storage version (hub) if no version specified
127-
if config.Features.Versioning.StorageVersion != "" {
128-
opts.version = config.Features.Versioning.StorageVersion
129-
fmt.Printf("No version specified, using storage hub version: %s\n", opts.version)
130-
} else {
131-
return fmt.Errorf("no --version specified and no storage_version configured.\nPlease specify a version with --version or set features.versioning.storage_version in .fabrica.yaml")
132-
}
133-
} else {
134-
// Validate version exists in config
135-
found := false
136-
for _, v := range config.Features.Versioning.Versions {
137-
if v == opts.version {
138-
found = true
139-
break
140-
}
141-
}
142-
if !found {
143-
return fmt.Errorf("version %s not found in .fabrica.yaml (available: %v)", opts.version, config.Features.Versioning.Versions)
144-
}
120+
group, err := apisConfig.primaryGroup()
121+
if err != nil {
122+
return err
123+
}
145124

146-
// Check if adding to non-alpha without --force
147-
if !strings.Contains(opts.version, "alpha") && !opts.force {
148-
return fmt.Errorf("adding resource to non-alpha version %s requires --force flag", opts.version)
125+
// Determine target version and directory
126+
isVersioned := true
127+
if opts.version == "" {
128+
opts.version = group.StorageVersion
129+
fmt.Printf("No version specified, using storage hub version: %s\n", opts.version)
130+
} else {
131+
found := false
132+
for _, v := range group.Versions {
133+
if v == opts.version {
134+
found = true
135+
break
149136
}
150137
}
138+
if !found {
139+
return fmt.Errorf("version %s not found in apis.yaml (available: %v)", opts.version, group.Versions)
140+
}
151141

152-
targetDir = filepath.Join("apis", config.Features.Versioning.Group, opts.version)
153-
} else {
154-
// Legacy mode is deprecated
155-
return fmt.Errorf("legacy mode (pkg/resources/) is deprecated.\nPlease enable versioning in .fabrica.yaml:\n\nfeatures:\n versioning:\n enabled: true\n group: your.api.group\n storage_version: v1\n versions:\n - v1")
142+
if !strings.Contains(opts.version, "alpha") && !opts.force {
143+
return fmt.Errorf("adding resource to non-alpha version %s requires --force flag", opts.version)
144+
}
156145
}
157146

147+
targetDir := filepath.Join("apis", group.Name, opts.version)
148+
158149
fmt.Printf("📦 Adding resource %s", resourceName)
159150
if isVersioned {
160-
fmt.Printf(" to %s/%s...\n", config.Features.Versioning.Group, opts.version)
151+
fmt.Printf(" to %s/%s...\n", group.Name, opts.version)
161152
} else {
162153
fmt.Println("...")
163154
}
@@ -175,27 +166,17 @@ func runAddResource(resourceName string, opts *addOptions) error {
175166
resourceFile = filepath.Join(targetDir, opts.packageName+".go")
176167
}
177168

178-
if err := generateResourceFile(resourceFile, resourceName, isVersioned, opts, config); err != nil {
169+
if err := generateResourceFile(resourceFile, resourceName, isVersioned, opts, config.Project.Module, group.StorageVersion, group.Name); err != nil {
179170
return err
180171
}
181172

182-
// Update config to add resource to versioning.resources list
173+
// Update apis.yaml to include the resource
183174
if isVersioned {
184-
// Check if resource already in list
185-
found := false
186-
for _, r := range config.Features.Versioning.Resources {
187-
if r == resourceName {
188-
found = true
189-
break
190-
}
191-
}
192-
if !found {
193-
config.Features.Versioning.Resources = append(config.Features.Versioning.Resources, resourceName)
194-
if err := SaveConfig("", config); err != nil {
195-
return fmt.Errorf("failed to update config: %w", err)
196-
}
197-
fmt.Printf(" ✓ Added %s to .fabrica.yaml\n", resourceName)
175+
apisConfig.addResource(resourceName)
176+
if err := SaveAPIsConfig("", apisConfig); err != nil {
177+
return fmt.Errorf("failed to update apis.yaml: %w", err)
198178
}
179+
fmt.Printf(" ✓ Added %s to apis.yaml\n", resourceName)
199180
}
200181

201182
fmt.Println()
@@ -215,7 +196,7 @@ func runAddResource(resourceName string, opts *addOptions) error {
215196
return nil
216197
}
217198

218-
func generateResourceFile(filePath, resourceName string, isVersioned bool, opts *addOptions, config *FabricaConfig) error {
199+
func generateResourceFile(filePath, resourceName string, isVersioned bool, opts *addOptions, modulePath, hubVersion, groupName string) error {
219200
var packageName string
220201
if isVersioned {
221202
// Use version as package name (e.g., v1alpha1)
@@ -225,10 +206,7 @@ func generateResourceFile(filePath, resourceName string, isVersioned bool, opts
225206
}
226207

227208
// Determine if this is the hub version (storage version)
228-
isHub := false
229-
if isVersioned && config != nil {
230-
isHub = (opts.version == config.Features.Versioning.StorageVersion)
231-
}
209+
isHub := isVersioned && hubVersion != "" && opts.version == hubVersion
232210

233211
content := fmt.Sprintf(`// Copyright © 2025 OpenCHAMI a Series of LF Projects, LLC
234212
//
@@ -245,14 +223,11 @@ import (
245223
"github.com/openchami/fabrica/pkg/fabrica"`
246224

247225
// Add hub package import for spoke versions (for conversions)
248-
if !isHub && config != nil {
249-
modulePath := config.Project.Module
250-
hubVersion := config.Features.Versioning.StorageVersion
251-
group := config.Features.Versioning.Group
226+
if !isHub && hubVersion != "" && groupName != "" && modulePath != "" {
252227
hubPackage := strings.ReplaceAll(hubVersion, ".", "")
253228

254229
content += `
255-
` + hubPackage + ` "` + modulePath + `/apis/` + group + `/` + hubVersion + `"`
230+
` + hubPackage + ` "` + modulePath + `/apis/` + groupName + `/` + hubVersion + `"`
256231
}
257232

258233
content += `
@@ -376,9 +351,7 @@ func (r *` + resourceName + `) IsHub() {}
376351
}
377352

378353
// Add conversion stubs for non-hub versions (spokes)
379-
if !isHub && config != nil {
380-
hubVersion := config.Features.Versioning.StorageVersion
381-
group := config.Features.Versioning.Group
354+
if !isHub && hubVersion != "" && groupName != "" {
382355
hubPackage := strings.ReplaceAll(hubVersion, ".", "")
383356

384357
content += `
@@ -389,7 +362,7 @@ func (src *` + resourceName + `) ConvertTo(dstRaw interface{}) error {
389362
// TODO: Implement conversion logic from ` + packageName + ` to ` + hubVersion + `
390363
391364
// Copy common fields
392-
dst.APIVersion = "` + group + `/` + hubVersion + `"
365+
dst.APIVersion = "` + groupName + `/` + hubVersion + `"
393366
dst.Kind = src.Kind
394367
dst.Metadata = src.Metadata
395368

0 commit comments

Comments
 (0)