···11+package validator
22+33+import (
44+ "database/sql"
55+ "fmt"
66+ "strings"
77+88+ "tangled.org/core/appview/db"
99+ "tangled.org/core/appview/models"
1010+ "tangled.org/core/orm"
1111+ "tangled.org/core/patchutil"
1212+)
1313+1414+func (v *Validator) ValidatePull(pull *models.Pull) error {
1515+ if len(pull.Submissions) == 0 {
1616+ return fmt.Errorf("pull must have at least one submission")
1717+ }
1818+1919+ latestSubmission := pull.LatestSubmission()
2020+ if latestSubmission == nil {
2121+ return fmt.Errorf("pull must have a valid latest submission")
2222+ }
2323+2424+ isFormatPatch := patchutil.IsFormatPatch(latestSubmission.Patch)
2525+2626+ // title and body can only be empty if the patch is a format-patch
2727+ if !isFormatPatch {
2828+ if pull.Title == "" {
2929+ return fmt.Errorf("pull title is empty (required for non-format-patch pulls)")
3030+ }
3131+3232+ if pull.Body == "" {
3333+ return fmt.Errorf("pull body is empty (required for non-format-patch pulls)")
3434+ }
3535+3636+ if st := strings.TrimSpace(v.sanitizer.SanitizeDescription(pull.Title)); st == "" {
3737+ return fmt.Errorf("title is empty after HTML sanitization")
3838+ }
3939+4040+ if sb := strings.TrimSpace(v.sanitizer.SanitizeDefault(pull.Body)); sb == "" {
4141+ return fmt.Errorf("body is empty after HTML sanitization")
4242+ }
4343+ }
4444+4545+ // the dependent_on should not form a DAG, aka, two PRs should not have the same dependent
4646+ if pull.DependentOn != nil {
4747+ dependentPull, err := db.GetPull(
4848+ v.db,
4949+ orm.FilterEq("dependent_on", pull.DependentOn.String()),
5050+ )
5151+5252+ if err == sql.ErrNoRows {
5353+ return nil
5454+ }
5555+5656+ if err != nil {
5757+ return fmt.Errorf("failed to fetch pulls with same dependency: %w", err)
5858+ }
5959+6060+ if dependentPull.AtUri() == pull.AtUri() {
6161+ return nil
6262+ }
6363+6464+ return fmt.Errorf("another pull already depends on %s, which would form a DAG, this is presently disallowed", pull.DependentOn.String())
6565+ }
6666+6767+ return nil
6868+}