Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 168 additions & 2 deletions hyperfleet/standards/generated-code-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## *Policy for managing generated code in HyperFleet repositories*

## Metadata

- **Date:** 2025-12-26
- **Authors:** Rafael Benevides
- **Status:** Active
Expand Down Expand Up @@ -71,8 +72,9 @@ Generated code refers to any files automatically created by tools from source sp
Each repository should add appropriate patterns to `.gitignore`. Note the distinction between **generated code** (auto-created from specs) and **fetched sources** (downloaded but not generated).

**hyperfleet-api:**

```gitignore
# Generated OpenAPI code
# Generated OpenAPI code (from oapi-codegen)
/pkg/api/openapi/
/data/generated/

Expand All @@ -81,6 +83,7 @@ Each repository should add appropriate patterns to `.gitignore`. Note the distin
```

**hyperfleet-sentinel:**

```gitignore
# Generated OpenAPI client (from oapi-codegen)
pkg/api/openapi/
Expand Down Expand Up @@ -191,7 +194,170 @@ fi

---

## 7. References
## 7. Code generator tool

We selected [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) tool as the generator for our apps.
Here is a detailed comparison among different alternatives:

### OpenAPI Code Generation Comparison

Overview

| Aspect | (OpenAPI Generator | ogen | oapi-codegen |
|-----------------|---------------------------|-------------------------------------------|----------------------|
| Files Generated | 34 | 20 | 2 |
| Lines of Code | ~11,274 | ~20,261 | ~2,530 |
| Runtime Deps | None (stdlib only) | ogen-go/ogen, go-faster/jx, OpenTelemetry | oapi-codegen/runtime |

---

#### 1. main/ - OpenAPI Generator (Java-based)

Type Style:

```
type Cluster struct {
CreatedTime time.Time `json:"created_time"`
Name string `json:"name" validate:"regexp=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"`
Spec map[string]interface{} `json:"spec"`
Labels *map[string]string `json:"labels,omitempty"` // pointer for optional
Id *string `json:"id,omitempty"`
}
```

Strengths:

- ✅ No runtime dependencies - uses only stdlib (encoding/json)
- ✅ Null-safety pattern with NullableCluster wrapper types
- ✅ Constructor functions (NewCluster, NewClusterWithDefaults)
- ✅ Validation in UnmarshalJSON - checks required properties
- ✅ GetXxxOk() methods return tuple (value, bool) for presence checking
- ✅ HasXxx() methods for optional field presence
- ✅ ToMap() method for generic map conversion
- ✅ Mature tooling - widely used, extensive documentation

Weaknesses:

- ❌ Verbose - each model in separate file with many boilerplate methods
- ❌ Pointer-based optionals (*string) - less idiomatic for Go
- ❌ No built-in validation beyond required field checking
- ❌ Flattens allOf schemas - loses composition structure
- ❌ Java dependency - requires JVM to run generator

---

#### 2. ogen/ (Go-native generator)

Type Style:

```
type Cluster struct {
ID OptString `json:"id"` // Optional type wrapper
Kind string `json:"kind"`
Labels OptClusterLabels `json:"labels"`
Name string `json:"name"`
Spec ClusterSpec `json:"spec"`
Generation int32 `json:"generation"`
Status ClusterStatus `json:"status"`
}

type OptString struct {
Value string
Set bool
}
```

Strengths:

- ✅ Opt[T] types for optionals - explicit presence tracking, no nil pointer issues
- ✅ Built-in validation (oas_validators_gen.go) with structured errors
- ✅ OpenTelemetry integration - tracing/metrics out of the box
- ✅ Enum validation with MarshalText/UnmarshalText
- ✅ High-performance JSON using go-faster/jx (no reflection)
- ✅ Generated getters/setters for all fields
- ✅ Pure Go toolchain - no JVM needed
- ✅ Server + Client generation in same package
- ✅ Type-safe response types (GetClusterByIdRes interface)

Weaknesses:

- ❌ Largest output (~20k lines) - more code to maintain
- ❌ Heavy runtime dependencies - ogen-go/ogen, go-faster/*, OTel
- ❌ Learning curve - Opt[T] pattern different from idiomatic Go
- ❌ Less flexibility - opinionated about patterns
- ❌ Flattens allOf - doesn't preserve schema composition

---

#### 3. oapi-codegen/ (Go-native generator)

Type Style:

```
type Cluster struct {
// Preserves allOf composition!
ClusterBase `yaml:",inline"`

CreatedBy openapi_types.Email `json:"created_by"` // Typed email
CreatedTime time.Time `json:"created_time"`
Generation int32 `json:"generation"`
Status ClusterStatus `json:"status"`
}

type ClusterBase struct {
APIResource `yaml:",inline"`
Kind string `json:"kind"`
Name string `json:"name"`
Spec ClusterSpec `json:"spec"`
}
```

Strengths:

- ✅ Most compact (~2.5k lines, 2 files) - minimal footprint
- ✅ Preserves allOf composition - embedded structs match schema
- ✅ Semantic types - openapi_types.Email instead of string
- ✅ Lightweight runtime - just oapi-codegen/runtime
- ✅ Pure Go toolchain - no JVM
- ✅ ClientWithResponses - parsed response bodies with type safety
- ✅ RequestEditorFn pattern - clean auth/middleware injection
- ✅ Go-idiomatic - feels like handwritten Go code

Weaknesses:

- ❌ No built-in validation - must add manually or use external
- ❌ Pointer-based optionals (*string) - though less pervasive
- ❌ Fewer accessor methods - direct field access preferred
- ❌ Less observability - no OTel integration
- ❌ Returns *http.Response - need ClientWithResponses for parsed bodies

---
Comparison Summary

| Feature | main/ | ogen/ | oapi-codegen/ |
|--------------------|-------------|--------------|---------------|
| Code Size | Medium | Large | Small ✅ |
| Runtime Deps | None ✅ | Heavy | Light |
| Optional Handling | Pointers | Opt[T] ✅ | Pointers |
| Validation | Basic | Full ✅ | None |
| Schema Composition | Flattened | Flattened | Preserved ✅ |
| Observability | None | OTel ✅ | None |
| Go Idiomaticity | Medium | Medium | High ✅ |
| Type Safety | Good | Excellent ✅ | Good |
| Maintenance | Java needed | Go | Go |

---
**Recommendation**

For our use case (types + client only):

- oapi-codegen is the best fit if you want minimal, Go-idiomatic code that preserves your schema composition (allOf inheritance). The embedded struct pattern (ClusterBase → Cluster) is clean and matches your OpenAPI design.
- ogen is better if you need built-in validation, observability (OTel), or are building a complete server+client solution. The Opt[T] pattern is cleaner than nil pointers.
- OpenAPI Generator (main/) is worth keeping if you need maximum compatibility or zero runtime dependencies, though the Java requirement and verbose output are downsides.

---

## 8. References

- [Makefile Conventions](makefile-conventions.md)
- [HYPERFLEET-303](https://issues.redhat.com/browse/HYPERFLEET-303)
Expand Down