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
99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
package contracts
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Dart — dio + package:http consumers
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Flutter apps are almost always the consumer side of HTTP contracts.
|
|
// Both dio and package:http accept a payload argument (`data:` or
|
|
// `body:`) and return a `Response`/`http.Response` that is then
|
|
// deserialised via `fromJson(...)`. We pick up the request type from
|
|
// the payload variable and the response type from the `fromJson`
|
|
// call's receiver.
|
|
|
|
func init() {
|
|
schemaEnrichers = append(schemaEnrichers,
|
|
schemaEnricher{
|
|
name: "dart-consumer",
|
|
languages: []string{"dart"},
|
|
roles: []Role{RoleConsumer},
|
|
detect: dartConsumerDetect,
|
|
},
|
|
)
|
|
}
|
|
|
|
var (
|
|
// dio.post(url, data: payload) — also handles keyword arg `data:`
|
|
// appearing anywhere in the call. `[^,)]*` bounds the identifier
|
|
// so we don't swallow an entire literal map.
|
|
dartDioDataRe = regexp.MustCompile(`data\s*:\s*([A-Za-z_$][\w$]*)`)
|
|
|
|
// http.post(url, body: jsonEncode(payload)) — common pattern for
|
|
// package:http. Same `body:` kwarg for custom wrappers.
|
|
dartBodyEncodeRe = regexp.MustCompile(`body\s*:\s*jsonEncode\(\s*([A-Za-z_$][\w$]*)\s*\)`)
|
|
|
|
// final parsed = FooResp.fromJson(resp.data) — the receiver of
|
|
// .fromJson is the response type.
|
|
dartFromJSONRe = regexp.MustCompile(`([A-Za-z_$][\w$]*)\.fromJson\(`)
|
|
|
|
// statusCode: 201 → present in dio request options but uncommon;
|
|
// response.statusCode reads aren't interesting here.
|
|
)
|
|
|
|
func dartConsumerDetect(body string, fileNodes []*graph.Node) schemaHints {
|
|
var h schemaHints
|
|
|
|
if m := dartDioDataRe.FindStringSubmatch(body); len(m) > 1 {
|
|
if rt := findDartVarType(body, m[1]); rt != "" {
|
|
h.RequestType = resolveTypeInFile(rt, fileNodes)
|
|
} else if h.RequestExpr == "" {
|
|
h.RequestExpr = "data: " + m[1]
|
|
}
|
|
} else if m := dartBodyEncodeRe.FindStringSubmatch(body); len(m) > 1 {
|
|
if rt := findDartVarType(body, m[1]); rt != "" {
|
|
h.RequestType = resolveTypeInFile(rt, fileNodes)
|
|
} else if h.RequestExpr == "" {
|
|
h.RequestExpr = "body: jsonEncode(" + m[1] + ")"
|
|
}
|
|
}
|
|
|
|
if m := dartFromJSONRe.FindStringSubmatch(body); len(m) > 1 {
|
|
h.ResponseType = resolveTypeInFile(m[1], fileNodes)
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
// findDartVarType mirrors findVarType / findTSVarType for Dart
|
|
// declaration forms. Covers:
|
|
//
|
|
// final name = Type(...)
|
|
// var name = Type(...)
|
|
// Type name = ...
|
|
// (..., Type name, ...) method parameter
|
|
//
|
|
// Dart's optional type syntax means many call sites use `final name`
|
|
// without a type annotation — we can't resolve those here.
|
|
func findDartVarType(body, varName string) string {
|
|
if varName == "" {
|
|
return ""
|
|
}
|
|
v := regexp.QuoteMeta(varName)
|
|
// final|var|const name = Type(
|
|
if m := regexp.MustCompile(`\b(?:final|var|const)\s+` + v + `\s*=\s*([A-Za-z_$][\w$.]*)\(`).FindStringSubmatch(body); len(m) > 1 {
|
|
return m[1]
|
|
}
|
|
// Type name = ... — leading identifier that looks like a type.
|
|
if m := regexp.MustCompile(`\b([A-Z][\w$]*(?:<[^>]+>)?)\s+` + v + `\b`).FindStringSubmatch(body); len(m) > 1 {
|
|
return strings.TrimSpace(stripGenerics(m[1]))
|
|
}
|
|
return ""
|
|
}
|