Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c7bf3eb
Add `staticconfig` package.
jmalloc Oct 15, 2024
5d0d447
Add support for static analysis of identities.
jmalloc Oct 17, 2024
96ae9f1
Remove `analyzeEntity`.
jmalloc Oct 18, 2024
a47227b
Detect speculative/conditional blocks.
jmalloc Oct 18, 2024
16bafc2
Add dead code elimination.
jmalloc Oct 19, 2024
5008892
Add support for "indirect" calls to the configurer.
jmalloc Oct 19, 2024
98265eb
Add tests for conditional defers.
jmalloc Oct 19, 2024
e3b85d1
Add some basic tests for generic types and functions.
jmalloc Oct 19, 2024
a543e56
Add tests for incomplete / unreachable code.
jmalloc Oct 19, 2024
23b82dd
Improve resolution of indirect constant values.
jmalloc Oct 20, 2024
2ef83c7
Add support for aliases.
jmalloc Oct 20, 2024
6096e76
Add test for invalid syntax.
jmalloc Oct 20, 2024
3b01e29
Add tests for registring nil handlers.
jmalloc Oct 20, 2024
2311dd3
Move generated test code under a subdirectory to avoid UI shift in IDE.
jmalloc Oct 21, 2024
98c699c
Add documentation to internal static value resolution code.
jmalloc Oct 21, 2024
836bf85
Fix directory trimming.
jmalloc Oct 21, 2024
0cead0c
Add `configureContext.Func`.
jmalloc Oct 21, 2024
98718c1
Use `staticValue()` to resolve conditionals.
jmalloc Oct 21, 2024
d1eae20
Add `transferOfControl()`.
jmalloc Oct 21, 2024
9627c5e
Add `analyzeHandler()`.
jmalloc Oct 22, 2024
6f14895
Add `ssax` package.
jmalloc Oct 22, 2024
c419faa
Add primitive support for `Routes()`.
jmalloc Oct 22, 2024
3b5f189
Add basic tests for remaining handler and route types.
jmalloc Oct 22, 2024
dcbd1d1
Write temporary files to directory under PID.
jmalloc Oct 23, 2024
42fd0f0
Refactor analysis code to use callbacks instead of iterators.
jmalloc Oct 23, 2024
14c0a18
Fix test title.
jmalloc Oct 23, 2024
b279e59
Move generic package/type/value analysis code to `ssax`.
jmalloc Oct 23, 2024
85a20c8
Rename `WalkDown()` to `WalkBlock()`.
jmalloc Oct 23, 2024
cb4644c
Update tests to work with new message formats.
jmalloc Nov 1, 2024
853be79
Genericize static analysis of variadic arguments.
jmalloc Nov 4, 2024
0a6c2ff
Record the reasons that a configuration is partially loaded.
jmalloc Nov 5, 2024
13f27ea
Remove debug code.
jmalloc Nov 5, 2024
04e17c4
Improve "speculative" detection of dynamically constructed variadics.
jmalloc Nov 5, 2024
566bd5f
Add tests for nil routes.
jmalloc Nov 6, 2024
d97328e
Add more tests for generic types.
jmalloc Nov 6, 2024
6e39eb5
Add tests for multiple handlers of the same type.
jmalloc Nov 6, 2024
7bc8434
Add tests for "generic adaptors".
jmalloc Nov 6, 2024
db8e5a1
WIP [ci skip]
jmalloc Nov 6, 2024
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
2 changes: 1 addition & 1 deletion config/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestAggregate(t *testing.T) {
Name: "nil aggregate",
Error: multiline(
`aggregate is invalid:`,
` - could not evaluate entire configuration`,
` - could not evaluate entire configuration: handler is nil`,
` - no identity`,
` - no handles-command routes`,
` - no records-event routes`,
Expand Down
2 changes: 1 addition & 1 deletion config/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestApplication(t *testing.T) {
Name: "nil application",
Error: multiline(
`application is invalid:`,
` - could not evaluate entire configuration`,
` - could not evaluate entire configuration: application is nil`,
` - no identity`,
),
Component: runtimeconfig.FromApplication(nil),
Expand Down
42 changes: 32 additions & 10 deletions config/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package config

import (
"fmt"
"strings"

"github.com/dogmatiq/enginekit/config/internal/renderer"
)

// Component is the "top-level" interface for the individual elements that form
Expand All @@ -24,12 +27,12 @@ type ComponentCommon struct {
// not be evaluated at configuration time.
IsSpeculative bool

// IsPartial indicates that the configuration could not be loaded in its
// entirety. The configuration may be valid, but cannot be safely used to
// execute an application.
// IsPartialReasons is a list of reasons that the configuration could not be
// loaded in its entirety. The configuration may be valid, but cannot be
// safely used to execute an application.
//
// A value of false does not imply a complete configuration.
IsPartial bool
// An empty slice does not imply a complete or valid configuration.
IsPartialReasons []string
}

// ComponentProperties returns the properties common to all [Component] types.
Expand All @@ -40,8 +43,8 @@ func (p *ComponentCommon) ComponentProperties() *ComponentCommon {
func validateComponent(ctx *validateContext) {
p := ctx.Component.ComponentProperties()

if p.IsPartial {
ctx.Invalid(PartialConfigurationError{})
if len(p.IsPartialReasons) != 0 {
ctx.Invalid(PartialConfigurationError{p.IsPartialReasons})
}

if ctx.Options.ForExecution && p.IsSpeculative {
Expand All @@ -63,16 +66,35 @@ func (e ConfigurationUnavailableError) Error() string {

// PartialConfigurationError indicates that a [Component]'s configuration could
// not be loaded in its entirety.
type PartialConfigurationError struct{}
type PartialConfigurationError struct {
Reasons []string
}

func (e PartialConfigurationError) Error() string {
return "could not evaluate entire configuration"
w := &strings.Builder{}
r := &renderer.Renderer{Target: w}

r.Print("could not evaluate entire configuration:")

if len(e.Reasons) == 1 {
r.Print(" ", e.Reasons[0])
} else if len(e.Reasons) > 1 {
for _, reason := range e.Reasons {
r.Print("\n")
r.StartChild()
r.Print(reason)
r.EndChild()
}
}

return w.String()
}

// SpeculativeConfigurationError indicates that a [Component]'s inclusion in the
// configuration is subject to some condition that could not be evaluated at the
// time the configuration was built.
type SpeculativeConfigurationError struct{}
type SpeculativeConfigurationError struct {
}

func (e SpeculativeConfigurationError) Error() string {
return "conditions for the component's inclusion in the configuration could not be evaluated"
Expand Down
2 changes: 1 addition & 1 deletion config/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func hasError[T error](ctx *describeContext) bool {
func (ctx *describeContext) DescribeFidelity() {
p := ctx.Component.ComponentProperties()

if p.IsPartial || hasError[PartialConfigurationError](ctx) || hasError[ConfigurationUnavailableError](ctx) {
if len(p.IsPartialReasons) != 0 || hasError[PartialConfigurationError](ctx) || hasError[ConfigurationUnavailableError](ctx) {
ctx.Print("incomplete ")
} else if !ctx.options.ValidationResult.IsPresent() {
ctx.Print("unvalidated ")
Expand Down
4 changes: 2 additions & 2 deletions config/entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func testEntity[
t.Run("it panics if the entity is partially configured", func(t *testing.T) {
entity := build(
func(b B) {
b.Partial()
b.Partial("<reason>")
b.Identity(
func(b *configbuilder.IdentityBuilder) {
b.Name("name")
Expand All @@ -65,7 +65,7 @@ func testEntity[

test.ExpectPanic(
t,
"could not evaluate entire configuration",
"could not evaluate entire configuration: <reason>",
func() {
entity.Identity()
},
Expand Down
4 changes: 2 additions & 2 deletions config/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ func testHandler[
handler := build(func(b B) {
b.Disabled(
func(b *configbuilder.FlagBuilder[Disabled]) {
b.Partial()
b.Partial("<reason>")
},
)
})

test.ExpectPanic(
t,
`flag:disabled is invalid: could not evaluate entire configuration`,
`flag:disabled is invalid: could not evaluate entire configuration: <reason>`,
func() {
handler.IsDisabled()
},
Expand Down
4 changes: 2 additions & 2 deletions config/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ func TestIdentity(t *testing.T) {
},
{
Name: "partial",
Error: `identity:name/e6b691dd-731c-4c14-8e1c-1622381202dc is invalid: could not evaluate entire configuration`,
Error: `identity:name/e6b691dd-731c-4c14-8e1c-1622381202dc is invalid: could not evaluate entire configuration: <reason>`,
Component: &Identity{
// It's possibly non-sensical to have an identity that contains
// both it's name and key be considered incomplete, but this
// allows us to represent a case where the name and key are
// build dynamically and we don't have the _entire_ string.
ComponentCommon: ComponentCommon{
IsPartial: true,
IsPartialReasons: []string{"<reason>"},
},
Name: optional.Some("name"),
Key: optional.Some("e6b691dd-731c-4c14-8e1c-1622381202dc"),
Expand Down
2 changes: 1 addition & 1 deletion config/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestIntegration(t *testing.T) {
Name: "nil integration",
Error: multiline(
`integration is invalid:`,
` - could not evaluate entire configuration`,
` - could not evaluate entire configuration: handler is nil`,
` - no identity`,
` - no handles-command routes`,
),
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/aggregate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
)
Expand Down Expand Up @@ -47,8 +49,8 @@ func (b *AggregateBuilder) Disabled(fn func(*FlagBuilder[config.Disabled])) {
}

// Partial marks the compomnent as partially configured.
func (b *AggregateBuilder) Partial() {
b.target.IsPartial = true
func (b *AggregateBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/application.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
)
Expand Down Expand Up @@ -59,8 +61,8 @@ func (b *ApplicationBuilder) Projection(fn func(*ProjectionBuilder)) {
}

// Partial marks the compomnent as partially configured.
func (b *ApplicationBuilder) Partial() {
b.target.IsPartial = true
func (b *ApplicationBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
10 changes: 8 additions & 2 deletions config/internal/configbuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import (
"github.com/dogmatiq/enginekit/optional"
)

// UntypedComponentBuilder is the interface for builders of some unknown
// [config.Component] type.
type UntypedComponentBuilder interface {
Partial(format string, args ...any)
Speculative()
}

// ComponentBuilder an interface for builders that produce a [config.Component].
type ComponentBuilder[T config.Component] interface {
Partial()
Speculative()
UntypedComponentBuilder
Done() T
}

Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/flag.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/enginekit/config"
"github.com/dogmatiq/enginekit/optional"
)
Expand All @@ -23,8 +25,8 @@ func (b *FlagBuilder[S]) Value(v bool) {
}

// Partial marks the compomnent as partially configured.
func (b *FlagBuilder[S]) Partial() {
b.target.IsPartial = true
func (b *FlagBuilder[S]) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/identity.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/enginekit/config"
"github.com/dogmatiq/enginekit/optional"
)
Expand Down Expand Up @@ -33,8 +35,8 @@ func (b *IdentityBuilder) Key(key string) {
}

// Partial marks the compomnent as partially configured.
func (b *IdentityBuilder) Partial() {
b.target.IsPartial = true
func (b *IdentityBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/integration.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
)
Expand Down Expand Up @@ -47,8 +49,8 @@ func (b *IntegrationBuilder) Disabled(fn func(*FlagBuilder[config.Disabled])) {
}

// Partial marks the compomnent as partially configured.
func (b *IntegrationBuilder) Partial() {
b.target.IsPartial = true
func (b *IntegrationBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/process.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
)
Expand Down Expand Up @@ -47,8 +49,8 @@ func (b *ProcessBuilder) Disabled(fn func(*FlagBuilder[config.Disabled])) {
}

// Partial marks the compomnent as partially configured.
func (b *ProcessBuilder) Partial() {
b.target.IsPartial = true
func (b *ProcessBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/projection.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
)
Expand Down Expand Up @@ -53,8 +55,8 @@ func (b *ProjectionBuilder) DeliveryPolicy(fn func(*ProjectionDeliveryPolicyBuil
}

// Partial marks the compomnent as partially configured.
func (b *ProjectionBuilder) Partial() {
b.target.IsPartial = true
func (b *ProjectionBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
6 changes: 4 additions & 2 deletions config/internal/configbuilder/projectiondeliverypolicy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configbuilder

import (
"fmt"

"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/config"
"github.com/dogmatiq/enginekit/optional"
Expand Down Expand Up @@ -46,8 +48,8 @@ func (b *ProjectionDeliveryPolicyBuilder) BroadcastToPrimaryFirst(v bool) {
}

// Partial marks the compomnent as partially configured.
func (b *ProjectionDeliveryPolicyBuilder) Partial() {
b.target.IsPartial = true
func (b *ProjectionDeliveryPolicyBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
5 changes: 3 additions & 2 deletions config/internal/configbuilder/route.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configbuilder

import (
"fmt"
"reflect"

"github.com/dogmatiq/dogma"
Expand Down Expand Up @@ -67,8 +68,8 @@ func (b *RouteBuilder) MessageType(t message.Type) {
}

// Partial marks the compomnent as partially configured.
func (b *RouteBuilder) Partial() {
b.target.IsPartial = true
func (b *RouteBuilder) Partial(format string, args ...any) {
b.target.IsPartialReasons = append(b.target.IsPartialReasons, fmt.Sprintf(format, args...))
}

// Speculative marks the component as speculative.
Expand Down
2 changes: 1 addition & 1 deletion config/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func TestProcess(t *testing.T) {
Name: "nil process",
Error: multiline(
`process is invalid:`,
` - could not evaluate entire configuration`,
` - could not evaluate entire configuration: handler is nil`,
` - no identity`,
` - no handles-event routes`,
` - no executes-command routes`,
Expand Down
6 changes: 3 additions & 3 deletions config/projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestProjection(t *testing.T) {
Name: "nil projection",
Error: multiline(
`projection is invalid:`,
` - could not evaluate entire configuration`,
` - could not evaluate entire configuration: handler is nil`,
` - no identity`,
` - no handles-event routes`,
),
Expand Down Expand Up @@ -426,13 +426,13 @@ func TestProjection(t *testing.T) {
t.Run("it panics if the handler is partially configured", func(t *testing.T) {
handler := configbuilder.Projection(
func(b *configbuilder.ProjectionBuilder) {
b.Partial()
b.Partial("<reason>")
},
)

test.ExpectPanic(
t,
"could not evaluate entire configuration",
`could not evaluate entire configuration: <reason>`,
func() {
handler.DeliveryPolicy()
},
Expand Down
Loading