123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package intstr
- import (
- "encoding/json"
- "errors"
- "fmt"
- "math"
- "runtime/debug"
- "strconv"
- "strings"
- "k8s.io/klog/v2"
- )
- // IntOrString is a type that can hold an int32 or a string. When used in
- // JSON or YAML marshalling and unmarshalling, it produces or consumes the
- // inner type. This allows you to have, for example, a JSON field that can
- // accept a name or number.
- // TODO: Rename to Int32OrString
- //
- // +protobuf=true
- // +protobuf.options.(gogoproto.goproto_stringer)=false
- // +k8s:openapi-gen=true
- type IntOrString struct {
- Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"`
- IntVal int32 `protobuf:"varint,2,opt,name=intVal"`
- StrVal string `protobuf:"bytes,3,opt,name=strVal"`
- }
- // Type represents the stored type of IntOrString.
- type Type int64
- const (
- Int Type = iota // The IntOrString holds an int.
- String // The IntOrString holds a string.
- )
- // FromInt creates an IntOrString object with an int32 value. It is
- // your responsibility not to call this method with a value greater
- // than int32.
- // Deprecated: use FromInt32 instead.
- func FromInt(val int) IntOrString {
- if val > math.MaxInt32 || val < math.MinInt32 {
- klog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack())
- }
- return IntOrString{Type: Int, IntVal: int32(val)}
- }
- // FromInt32 creates an IntOrString object with an int32 value.
- func FromInt32(val int32) IntOrString {
- return IntOrString{Type: Int, IntVal: val}
- }
- // FromString creates an IntOrString object with a string value.
- func FromString(val string) IntOrString {
- return IntOrString{Type: String, StrVal: val}
- }
- // Parse the given string and try to convert it to an integer before
- // setting it as a string value.
- func Parse(val string) IntOrString {
- i, err := strconv.Atoi(val)
- if err != nil {
- return FromString(val)
- }
- return FromInt(i)
- }
- // UnmarshalJSON implements the json.Unmarshaller interface.
- func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
- if value[0] == '"' {
- intstr.Type = String
- return json.Unmarshal(value, &intstr.StrVal)
- }
- intstr.Type = Int
- return json.Unmarshal(value, &intstr.IntVal)
- }
- // String returns the string value, or the Itoa of the int value.
- func (intstr *IntOrString) String() string {
- if intstr == nil {
- return "<nil>"
- }
- if intstr.Type == String {
- return intstr.StrVal
- }
- return strconv.Itoa(intstr.IntValue())
- }
- // IntValue returns the IntVal if type Int, or if
- // it is a String, will attempt a conversion to int,
- // returning 0 if a parsing error occurs.
- func (intstr *IntOrString) IntValue() int {
- if intstr.Type == String {
- i, _ := strconv.Atoi(intstr.StrVal)
- return i
- }
- return int(intstr.IntVal)
- }
- // MarshalJSON implements the json.Marshaller interface.
- func (intstr IntOrString) MarshalJSON() ([]byte, error) {
- switch intstr.Type {
- case Int:
- return json.Marshal(intstr.IntVal)
- case String:
- return json.Marshal(intstr.StrVal)
- default:
- return []byte{}, fmt.Errorf("impossible IntOrString.Type")
- }
- }
- // OpenAPISchemaType is used by the kube-openapi generator when constructing
- // the OpenAPI spec of this type.
- //
- // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
- func (IntOrString) OpenAPISchemaType() []string { return []string{"string"} }
- // OpenAPISchemaFormat is used by the kube-openapi generator when constructing
- // the OpenAPI spec of this type.
- func (IntOrString) OpenAPISchemaFormat() string { return "int-or-string" }
- // OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing
- // the OpenAPI v3 spec of this type.
- func (IntOrString) OpenAPIV3OneOfTypes() []string { return []string{"integer", "string"} }
- func ValueOrDefault(intOrPercent *IntOrString, defaultValue IntOrString) *IntOrString {
- if intOrPercent == nil {
- return &defaultValue
- }
- return intOrPercent
- }
- // GetScaledValueFromIntOrPercent is meant to replace GetValueFromIntOrPercent.
- // This method returns a scaled value from an IntOrString type. If the IntOrString
- // is a percentage string value it's treated as a percentage and scaled appropriately
- // in accordance to the total, if it's an int value it's treated as a simple value and
- // if it is a string value which is either non-numeric or numeric but lacking a trailing '%' it returns an error.
- func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
- if intOrPercent == nil {
- return 0, errors.New("nil value for IntOrString")
- }
- value, isPercent, err := getIntOrPercentValueSafely(intOrPercent)
- if err != nil {
- return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
- }
- if isPercent {
- if roundUp {
- value = int(math.Ceil(float64(value) * (float64(total)) / 100))
- } else {
- value = int(math.Floor(float64(value) * (float64(total)) / 100))
- }
- }
- return value, nil
- }
- // GetValueFromIntOrPercent was deprecated in favor of
- // GetScaledValueFromIntOrPercent. This method was treating all int as a numeric value and all
- // strings with or without a percent symbol as a percentage value.
- // Deprecated
- func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
- if intOrPercent == nil {
- return 0, errors.New("nil value for IntOrString")
- }
- value, isPercent, err := getIntOrPercentValue(intOrPercent)
- if err != nil {
- return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
- }
- if isPercent {
- if roundUp {
- value = int(math.Ceil(float64(value) * (float64(total)) / 100))
- } else {
- value = int(math.Floor(float64(value) * (float64(total)) / 100))
- }
- }
- return value, nil
- }
- // getIntOrPercentValue is a legacy function and only meant to be called by GetValueFromIntOrPercent
- // For a more correct implementation call getIntOrPercentSafely
- func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) {
- switch intOrStr.Type {
- case Int:
- return intOrStr.IntValue(), false, nil
- case String:
- s := strings.Replace(intOrStr.StrVal, "%", "", -1)
- v, err := strconv.Atoi(s)
- if err != nil {
- return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
- }
- return int(v), true, nil
- }
- return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
- }
- func getIntOrPercentValueSafely(intOrStr *IntOrString) (int, bool, error) {
- switch intOrStr.Type {
- case Int:
- return intOrStr.IntValue(), false, nil
- case String:
- isPercent := false
- s := intOrStr.StrVal
- if strings.HasSuffix(s, "%") {
- isPercent = true
- s = strings.TrimSuffix(intOrStr.StrVal, "%")
- } else {
- return 0, false, fmt.Errorf("invalid type: string is not a percentage")
- }
- v, err := strconv.Atoi(s)
- if err != nil {
- return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
- }
- return int(v), isPercent, nil
- }
- return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
- }
|