202 lines
7.9 KiB
Go
202 lines
7.9 KiB
Go
package engine
|
|
|
|
type collectionPolicy struct {
|
|
identityFields []string
|
|
identityPresent bool
|
|
identityLocation *Location
|
|
explicitMode sequenceMode
|
|
explicitModePresent bool
|
|
modeLocation *Location
|
|
recordOrder []*collectionRecordOwner
|
|
recordPolicies map[*collectionRecordOwner]collectionPolicyScope
|
|
}
|
|
|
|
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) {
|
|
normalizedDeclarations := cloneSequenceDeclarations(declarations)
|
|
for index := range normalizedDeclarations {
|
|
declaration := &normalizedDeclarations[index]
|
|
if !declaration.identityPresent {
|
|
continue
|
|
}
|
|
fields, err := normalizeSequenceIdentityFields(declaration.identityFields)
|
|
if err != nil {
|
|
return collectionPolicyResolution{}, []Diagnostic{{
|
|
Code: CodeSequenceInvalid, Severity: SeverityError, Path: destination.clone(),
|
|
Message: "collection identity declaration requires at least one field",
|
|
Primary: collectionDeclarationLocation(declaration.identityLocation, declaration.location),
|
|
}}
|
|
}
|
|
declaration.identityFields = fields
|
|
}
|
|
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: normalizedDeclarations,
|
|
declarationsPresent: len(normalizedDeclarations) != 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 normalizedDeclarations {
|
|
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
|
|
}
|