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) } else if lower != nil && lower.explicitModePresent { result.policy.explicitMode = lower.explicitMode result.policy.explicitModePresent = true result.policy.modeLocation = cloneLocation(lower.modeLocation) } result.operation = collectionOperationMode(declaredMode, result.policy.explicitMode, len(result.effectiveIdentityFields) != 0) return result, nil } func collectionOperationMode(current, retained sequenceMode, identified bool) sequenceMode { if current != 0 { return current } if retained != 0 { return retained } if identified { return sequenceModeUpsert } return sequenceModeReplace } 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 }