mirror of
https://gh-proxy.org/https://github.com/klinsmaya/new-api.git
synced 2026-11-08 01:17:09 +08:00
Add img_cr and bounded image_count inputs, expose settled billing inputs, and reuse expression digests. Extend the shared frontend contract fixtures and billing documentation.
88 lines
3.0 KiB
Go
88 lines
3.0 KiB
Go
package billingexpr
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/expr-lang/expr/ast"
|
|
)
|
|
|
|
// UsesFixedPricing includes unselected branches, even when compilation later
|
|
// optimizes them away. Hosts use it to reject unsupported billing entrances.
|
|
func UsesFixedPricing(expression string) bool {
|
|
return UsesFixedPricingByHash(expression, ExprHashString(expression))
|
|
}
|
|
|
|
// UsesFixedPricingByHash avoids hashing an expression already in a snapshot.
|
|
func UsesFixedPricingByHash(expression, hash string) bool {
|
|
entry, err := compileEntryFromCacheByHash(expression, hash)
|
|
return err == nil && entry.fixedPricing
|
|
}
|
|
|
|
func containsPricingMarker(node ast.Node) bool {
|
|
return ast.Find(node, func(part ast.Node) bool {
|
|
identifier, ok := part.(*ast.IdentifierNode)
|
|
return ok && (identifier.Value == "tier" || identifier.Value == "fixed")
|
|
}) != nil
|
|
}
|
|
|
|
func isRequestPriceMultiplier(node ast.Node) bool {
|
|
if identifier, ok := node.(*ast.IdentifierNode); ok && identifier.Value == "image_count" {
|
|
return true
|
|
}
|
|
conditional, ok := node.(*ast.ConditionalNode)
|
|
if !ok || !usesRequestProbe(conditional.Cond) || containsPricingMarker(conditional.Cond) {
|
|
return false
|
|
}
|
|
multiplier, multiplierOK := requestRuleNumber(conditional.Exp1)
|
|
fallback, fallbackOK := requestRuleNumber(conditional.Exp2)
|
|
return multiplierOK && fallbackOK && fallback == 1 && multiplier >= 0 && !math.IsNaN(multiplier) && !math.IsInf(multiplier, 0)
|
|
}
|
|
|
|
// validateFixedPricingTree enforces one pricing leaf per execution. Without
|
|
// this invariant, adding two tiers or multiplying a fixed price by tokens
|
|
// would make both the request charge and its billing-unit trace ambiguous.
|
|
func validateFixedPricingTree(node ast.Node) error {
|
|
switch part := node.(type) {
|
|
case *ast.ConditionalNode:
|
|
if containsPricingMarker(part.Cond) {
|
|
break
|
|
}
|
|
if err := validateFixedPricingTree(part.Exp1); err != nil {
|
|
return err
|
|
}
|
|
return validateFixedPricingTree(part.Exp2)
|
|
case *ast.BinaryNode:
|
|
if part.Operator != "*" {
|
|
break
|
|
}
|
|
if isRequestPriceMultiplier(part.Right) {
|
|
return validateFixedPricingTree(part.Left)
|
|
}
|
|
if isRequestPriceMultiplier(part.Left) {
|
|
return validateFixedPricingTree(part.Right)
|
|
}
|
|
case *ast.CallNode:
|
|
callee, ok := part.Callee.(*ast.IdentifierNode)
|
|
if !ok || callee.Value != "tier" || len(part.Arguments) != 2 || containsPricingMarker(part.Arguments[0]) {
|
|
break
|
|
}
|
|
price := part.Arguments[1]
|
|
fixed, ok := price.(*ast.CallNode)
|
|
if ok {
|
|
function, direct := fixed.Callee.(*ast.IdentifierNode)
|
|
if direct && function.Value == "fixed" && len(fixed.Arguments) == 1 {
|
|
amount, literal := requestRuleNumber(fixed.Arguments[0])
|
|
if literal && amount >= 0 && !math.IsNaN(amount) && !math.IsInf(amount*1_000_000, 0) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("fixed price must be a finite, non-negative numeric literal with a finite v1 value")
|
|
}
|
|
}
|
|
if !containsPricingMarker(price) {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("fixed pricing requires tier(name, fixed(amount)) leaves, conditional tiers and request multipliers; token and fixed charges cannot be combined in one leaf")
|
|
}
|