Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
130 lines
2.5 KiB
Go
130 lines
2.5 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"unicode"
|
|
|
|
opl "github.com/ory/keto/gen/go/ory/keto/opl/v1alpha1"
|
|
"github.com/ory/keto/ketoapi"
|
|
)
|
|
|
|
type (
|
|
ParseError struct {
|
|
msg string
|
|
item item
|
|
p *parser
|
|
}
|
|
)
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (e *ParseError) Error() string {
|
|
var s strings.Builder
|
|
start := e.toSrcPos(e.item.Start)
|
|
end := e.toSrcPos(e.item.End)
|
|
rows := e.rows()
|
|
startLineIdx := max(start.Line-2, 0)
|
|
errorLineIdx := max(start.Line-1, 0)
|
|
|
|
_, _ = fmt.Fprintf(&s, "error from %d:%d to %d:%d: %s\n\n",
|
|
start.Line, start.Col,
|
|
end.Line, end.Col,
|
|
e.msg)
|
|
|
|
if len(rows) < start.Line {
|
|
s.WriteString("meta error: could not find source position in input\n")
|
|
return s.String()
|
|
}
|
|
|
|
for line := startLineIdx; line <= errorLineIdx; line++ {
|
|
_, _ = fmt.Fprintf(&s, "%4d | %s\n", line, rows[line])
|
|
}
|
|
s.WriteString(" | ")
|
|
for i, r := range rows[errorLineIdx] {
|
|
switch {
|
|
case start.Col == i:
|
|
s.WriteRune('^')
|
|
case start.Col <= i && i <= end.Col-1:
|
|
s.WriteRune('~')
|
|
case unicode.IsSpace(r):
|
|
s.WriteRune(r)
|
|
default:
|
|
s.WriteRune(' ')
|
|
}
|
|
}
|
|
s.WriteRune('\n')
|
|
|
|
if errorLineIdx+1 < len(rows) {
|
|
_, _ = fmt.Fprintf(&s, "%4d | %s\n", errorLineIdx+1, rows[errorLineIdx+1])
|
|
s.WriteRune('\n')
|
|
}
|
|
|
|
return s.String()
|
|
}
|
|
|
|
func (e *ParseError) ToAPI() *ketoapi.ParseError {
|
|
return &ketoapi.ParseError{
|
|
Message: e.msg,
|
|
Start: e.toSrcPos(e.item.Start),
|
|
End: e.toSrcPos(e.item.End),
|
|
}
|
|
}
|
|
func (e *ParseError) ToProto() *opl.ParseError {
|
|
start := e.toSrcPos(e.item.Start)
|
|
end := e.toSrcPos(e.item.End)
|
|
return &opl.ParseError{
|
|
Message: e.msg,
|
|
Start: &opl.SourcePosition{
|
|
Line: clampUint32(start.Line),
|
|
Column: clampUint32(start.Col),
|
|
},
|
|
End: &opl.SourcePosition{
|
|
Line: clampUint32(end.Line),
|
|
Column: clampUint32(end.Col),
|
|
},
|
|
}
|
|
}
|
|
|
|
// toSrcPos converts the given position in the input to a Line and column
|
|
// number.
|
|
func (e *ParseError) toSrcPos(pos int) (srcPos ketoapi.SourcePosition) {
|
|
srcPos.Line = 1
|
|
for _, c := range e.p.lexer.input {
|
|
srcPos.Col++
|
|
pos--
|
|
if pos <= 0 {
|
|
break
|
|
}
|
|
if c == '\n' {
|
|
srcPos.Line++
|
|
srcPos.Col = 0
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func clampUint32(v int) uint32 {
|
|
switch {
|
|
case v < 0:
|
|
return 0
|
|
case uint64(v) > math.MaxUint32:
|
|
return math.MaxUint32
|
|
default:
|
|
return uint32(v)
|
|
}
|
|
}
|
|
|
|
func (e *ParseError) rows() []string {
|
|
return strings.Split(e.p.lexer.input, "\n")
|
|
}
|