a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package contracts
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// rustFieldLineRe captures a serde-serialisable struct field line.
|
|
// Visibility qualifier + name + type:
|
|
//
|
|
// pub name: String,
|
|
// pub(crate) name: Option<String>,
|
|
// name: Vec<T>, (private — still indexable)
|
|
//
|
|
// Generic arguments are kept in the capture so container detection
|
|
// (`Vec<T>`, `HashMap<K,V>`) and optionality (`Option<T>`) can be
|
|
// read out of the type string.
|
|
var rustFieldLineRe = regexp.MustCompile(
|
|
`^\s*(?:pub(?:\([^)]*\))?\s+)?(\w+)\s*:\s*([A-Za-z_][\w:]*(?:<[^>]+>)?)\s*,?\s*$`,
|
|
)
|
|
|
|
// rustSerdeAttrRe catches serde attribute macros on the preceding
|
|
// lines. `#[serde(rename = "x")]`, `#[serde(skip_serializing_if = "...", default)]`.
|
|
var rustSerdeAttrRe = regexp.MustCompile(`#\[serde\(([^)]*)\)\]`)
|
|
|
|
// rustSerdeRenameRe isolates the `rename = "wire"` assignment inside
|
|
// a serde attribute body.
|
|
var rustSerdeRenameRe = regexp.MustCompile(`rename\s*=\s*"([^"]+)"`)
|
|
|
|
// extractRustShape reads a Rust struct body and returns its fields.
|
|
// Enum variants are not currently handled — contracts rarely use
|
|
// enum bodies directly, and when they do (tagged unions) they need
|
|
// bespoke serde handling that's beyond scope here.
|
|
func extractRustShape(src []byte, startLine, endLine int) *Shape {
|
|
body := sliceBody(src, startLine, endLine)
|
|
if body == "" {
|
|
body = braceBody(src, startLine, 400)
|
|
}
|
|
openIdx := strings.Index(body, "{")
|
|
if openIdx < 0 {
|
|
return nil
|
|
}
|
|
depth := 0
|
|
closeIdx := -1
|
|
for i := openIdx; i < len(body); i++ {
|
|
switch body[i] {
|
|
case '{':
|
|
depth++
|
|
case '}':
|
|
depth--
|
|
if depth == 0 {
|
|
closeIdx = i
|
|
}
|
|
}
|
|
if closeIdx >= 0 {
|
|
break
|
|
}
|
|
}
|
|
if closeIdx < 0 {
|
|
return nil
|
|
}
|
|
inner := body[openIdx+1 : closeIdx]
|
|
|
|
lines := strings.Split(inner, "\n")
|
|
shape := &Shape{Kind: "struct"}
|
|
var pendingAttrs []string
|
|
for _, raw := range lines {
|
|
line := strings.TrimSpace(raw)
|
|
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "///") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "#[") {
|
|
pendingAttrs = append(pendingAttrs, line)
|
|
continue
|
|
}
|
|
m := rustFieldLineRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
pendingAttrs = nil
|
|
continue
|
|
}
|
|
name := m[1]
|
|
typeExpr := strings.TrimSpace(m[2])
|
|
|
|
// serde attribute parsing.
|
|
var serdeTag string
|
|
skipSerializing := false
|
|
hasDefault := false
|
|
for _, ann := range pendingAttrs {
|
|
am := rustSerdeAttrRe.FindStringSubmatch(ann)
|
|
if am == nil {
|
|
continue
|
|
}
|
|
contents := am[1]
|
|
if rm := rustSerdeRenameRe.FindStringSubmatch(contents); len(rm) > 1 {
|
|
serdeTag = rm[1]
|
|
}
|
|
if strings.Contains(contents, "skip_serializing_if") {
|
|
skipSerializing = true
|
|
}
|
|
if strings.Contains(contents, "default") {
|
|
hasDefault = true
|
|
}
|
|
}
|
|
pendingAttrs = nil
|
|
|
|
nullable := strings.HasPrefix(typeExpr, "Option<") || skipSerializing || hasDefault
|
|
repeated := strings.HasPrefix(typeExpr, "Vec<") ||
|
|
strings.HasPrefix(typeExpr, "HashSet<") ||
|
|
strings.HasPrefix(typeExpr, "BTreeSet<")
|
|
effective := stripGenericsRust(typeExpr)
|
|
// Peel one level of Option / Box / Vec for the display type
|
|
// so `Option<String>` shows as `String` with ?, `Vec<Item>`
|
|
// shows as `Item[]`, mirroring other languages.
|
|
if strings.HasPrefix(typeExpr, "Option<") && strings.HasSuffix(typeExpr, ">") {
|
|
effective = stripGenericsRust(strings.TrimSuffix(strings.TrimPrefix(typeExpr, "Option<"), ">"))
|
|
}
|
|
if repeated {
|
|
// Strip the outer container.
|
|
idx := strings.Index(typeExpr, "<")
|
|
if idx >= 0 {
|
|
effective = stripGenericsRust(strings.TrimSuffix(typeExpr[idx+1:], ">"))
|
|
}
|
|
}
|
|
|
|
wireName := name
|
|
if serdeTag != "" {
|
|
wireName = serdeTag
|
|
}
|
|
shape.Fields = append(shape.Fields, ShapeField{
|
|
Name: wireName,
|
|
Type: stripRustPath(effective),
|
|
JSONTag: serdeTag,
|
|
Required: !nullable,
|
|
Repeated: repeated,
|
|
})
|
|
}
|
|
if len(shape.Fields) == 0 {
|
|
return nil
|
|
}
|
|
return shape
|
|
}
|