Reconcile collection declarations and modes
This commit is contained in:
183
internal/engine/collection_policy.go
Normal file
183
internal/engine/collection_policy.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package engine
|
||||
|
||||
type collectionPolicy struct {
|
||||
identityFields []string
|
||||
identityPresent bool
|
||||
identityLocation *Location
|
||||
explicitMode sequenceMode
|
||||
explicitModePresent bool
|
||||
modeLocation *Location
|
||||
}
|
||||
|
||||
type collectionPolicyResolution struct {
|
||||
policy collectionPolicy
|
||||
operation sequenceMode
|
||||
declarations []sequenceDeclarationSpec
|
||||
declarationsPresent bool
|
||||
identityDeclarationPresent bool
|
||||
modeDeclarationPresent bool
|
||||
applicationIdentityFields []string
|
||||
applicationIdentityPresent bool
|
||||
effectiveIdentityFields []string
|
||||
}
|
||||
|
||||
// resolveCollectionPolicy reconciles declarations for one destination before
|
||||
// peers are composed. The caller supplies only lower policy that is applicable
|
||||
// to this destination; record survival and ownership are resolved elsewhere.
|
||||
func resolveCollectionPolicy(
|
||||
destination Path,
|
||||
declarations []sequenceDeclarationSpec,
|
||||
applicationIdentityFields []string,
|
||||
lower *collectionPolicy,
|
||||
) (collectionPolicyResolution, []Diagnostic) {
|
||||
normalizedApplicationIdentityFields := cloneStrings(applicationIdentityFields)
|
||||
if len(applicationIdentityFields) != 0 {
|
||||
var err error
|
||||
normalizedApplicationIdentityFields, err = normalizeSequenceIdentityFields(applicationIdentityFields)
|
||||
if err != nil {
|
||||
return collectionPolicyResolution{}, []Diagnostic{{
|
||||
Code: CodeOptionsInvalid, Severity: SeverityError, Path: destination.clone(),
|
||||
Message: "application collection identity fields are invalid",
|
||||
}}
|
||||
}
|
||||
}
|
||||
result := collectionPolicyResolution{
|
||||
declarations: cloneSequenceDeclarations(declarations),
|
||||
declarationsPresent: len(declarations) != 0,
|
||||
applicationIdentityFields: normalizedApplicationIdentityFields,
|
||||
applicationIdentityPresent: len(normalizedApplicationIdentityFields) != 0,
|
||||
}
|
||||
|
||||
var diagnostics []Diagnostic
|
||||
var declaredIdentityFields []string
|
||||
var declaredIdentityLocation *Location
|
||||
var declaredMode sequenceMode
|
||||
var declaredModeLocation *Location
|
||||
for _, declaration := range declarations {
|
||||
if declaration.identityPresent {
|
||||
result.identityDeclarationPresent = true
|
||||
if declaredIdentityFields == nil {
|
||||
declaredIdentityFields = cloneStrings(declaration.identityFields)
|
||||
declaredIdentityLocation = collectionDeclarationLocation(declaration.identityLocation, declaration.location)
|
||||
} else if !equalSequenceIdentityFields(declaredIdentityFields, declaration.identityFields) {
|
||||
diagnostics = append(diagnostics, collectionPolicyConflictDiagnostic(
|
||||
destination, declaredIdentityLocation,
|
||||
collectionDeclarationLocation(declaration.identityLocation, declaration.location),
|
||||
"collection identity declarations conflict",
|
||||
))
|
||||
}
|
||||
}
|
||||
if declaration.modePresent {
|
||||
result.modeDeclarationPresent = true
|
||||
if declaredMode == 0 {
|
||||
declaredMode = declaration.mode
|
||||
declaredModeLocation = collectionDeclarationLocation(declaration.modeLocation, declaration.location)
|
||||
} else if declaredMode != declaration.mode {
|
||||
diagnostics = append(diagnostics, collectionPolicyConflictDiagnostic(
|
||||
destination, declaredModeLocation,
|
||||
collectionDeclarationLocation(declaration.modeLocation, declaration.location),
|
||||
"collection operation declarations conflict",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(diagnostics) != 0 {
|
||||
return collectionPolicyResolution{}, canonicalDiagnostics(diagnostics)
|
||||
}
|
||||
|
||||
if lower != nil && lower.identityPresent && declaredIdentityFields != nil && !equalSequenceIdentityFields(lower.identityFields, declaredIdentityFields) {
|
||||
diagnostics = append(diagnostics, collectionPolicyConflictDiagnostic(
|
||||
destination, declaredIdentityLocation,
|
||||
collectionDeclarationLocation(lower.identityLocation, Location{}),
|
||||
"collection identity declaration conflicts with retained identity",
|
||||
))
|
||||
}
|
||||
if result.applicationIdentityPresent {
|
||||
if declaredIdentityFields != nil && !equalSequenceIdentityFields(normalizedApplicationIdentityFields, declaredIdentityFields) {
|
||||
diagnostics = append(diagnostics, collectionPolicyConflictDiagnostic(
|
||||
destination, declaredIdentityLocation, nil,
|
||||
"collection identity declaration conflicts with application constraint",
|
||||
))
|
||||
}
|
||||
if lower != nil && lower.identityPresent && !equalSequenceIdentityFields(normalizedApplicationIdentityFields, lower.identityFields) {
|
||||
diagnostics = append(diagnostics, collectionPolicyConflictDiagnostic(
|
||||
destination, declaredIdentityLocation,
|
||||
collectionDeclarationLocation(lower.identityLocation, Location{}),
|
||||
"retained collection identity conflicts with application constraint",
|
||||
))
|
||||
}
|
||||
}
|
||||
if len(diagnostics) != 0 {
|
||||
return collectionPolicyResolution{}, canonicalDiagnostics(diagnostics)
|
||||
}
|
||||
|
||||
if declaredIdentityFields != nil {
|
||||
result.policy.identityFields = declaredIdentityFields
|
||||
result.policy.identityPresent = true
|
||||
result.policy.identityLocation = cloneLocation(declaredIdentityLocation)
|
||||
} else if lower != nil && lower.identityPresent {
|
||||
result.policy.identityFields = cloneStrings(lower.identityFields)
|
||||
result.policy.identityPresent = true
|
||||
result.policy.identityLocation = cloneLocation(lower.identityLocation)
|
||||
}
|
||||
if result.applicationIdentityPresent {
|
||||
result.effectiveIdentityFields = cloneStrings(normalizedApplicationIdentityFields)
|
||||
} else if result.policy.identityPresent {
|
||||
result.effectiveIdentityFields = cloneStrings(result.policy.identityFields)
|
||||
}
|
||||
|
||||
if result.modeDeclarationPresent {
|
||||
result.policy.explicitMode = declaredMode
|
||||
result.policy.explicitModePresent = true
|
||||
result.policy.modeLocation = cloneLocation(declaredModeLocation)
|
||||
result.operation = declaredMode
|
||||
} else if lower != nil && lower.explicitModePresent {
|
||||
result.policy.explicitMode = lower.explicitMode
|
||||
result.policy.explicitModePresent = true
|
||||
result.policy.modeLocation = cloneLocation(lower.modeLocation)
|
||||
result.operation = lower.explicitMode
|
||||
} else if len(result.effectiveIdentityFields) != 0 {
|
||||
result.operation = sequenceModeUpsert
|
||||
} else {
|
||||
result.operation = sequenceModeReplace
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func cloneSequenceDeclarations(declarations []sequenceDeclarationSpec) []sequenceDeclarationSpec {
|
||||
if declarations == nil {
|
||||
return nil
|
||||
}
|
||||
cloned := make([]sequenceDeclarationSpec, len(declarations))
|
||||
for index, declaration := range declarations {
|
||||
cloned[index] = declaration
|
||||
cloned[index].path = declaration.path.clone()
|
||||
cloned[index].destination = declaration.destination.clone()
|
||||
cloned[index].identityFields = cloneStrings(declaration.identityFields)
|
||||
cloned[index].identityLocation = cloneLocation(declaration.identityLocation)
|
||||
cloned[index].modeLocation = cloneLocation(declaration.modeLocation)
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func collectionDeclarationLocation(specific *Location, fallback Location) *Location {
|
||||
if specific != nil {
|
||||
return cloneLocation(specific)
|
||||
}
|
||||
if fallback != (Location{}) {
|
||||
return cloneLocation(&fallback)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectionPolicyConflictDiagnostic(path Path, primary, related *Location, message string) Diagnostic {
|
||||
diagnostic := Diagnostic{
|
||||
Code: CodeSequenceConflict, Severity: SeverityError,
|
||||
Path: path.clone(), Message: message, Primary: cloneLocation(primary),
|
||||
}
|
||||
if related != nil {
|
||||
diagnostic.Related = []Location{*cloneLocation(related)}
|
||||
}
|
||||
return diagnostic
|
||||
}
|
||||
Reference in New Issue
Block a user