From 259b1e0efa876d11954b987e9255de91b9aa2d88 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 13:56:15 +0800 Subject: [PATCH 01/12] CP-47361: generate mustache template for deserialize and serialize functions Signed-off-by: xueqingz Signed-off-by: Luca Zhang --- .../go/templates/ConvertBatch.mustache | 20 +++++++ .../sdk-gen/go/templates/ConvertEnum.mustache | 25 +++++++++ .../go/templates/ConvertFloat.mustache | 38 ++++++++++++++ .../sdk-gen/go/templates/ConvertInt.mustache | 25 +++++++++ .../go/templates/ConvertInterface.mustache | 8 +++ .../sdk-gen/go/templates/ConvertMap.mustache | 43 +++++++++++++++ .../go/templates/ConvertOption.mustache | 27 ++++++++++ .../go/templates/ConvertRecord.mustache | 52 +++++++++++++++++++ .../sdk-gen/go/templates/ConvertRef.mustache | 18 +++++++ .../sdk-gen/go/templates/ConvertSet.mustache | 35 +++++++++++++ .../go/templates/ConvertSimpleType.mustache | 20 +++++++ .../sdk-gen/go/templates/ConvertTime.mustache | 34 ++++++++++++ 12 files changed, 345 insertions(+) create mode 100644 ocaml/sdk-gen/go/templates/ConvertBatch.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertEnum.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertFloat.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertInt.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertInterface.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertMap.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertOption.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertRecord.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertRef.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertSet.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertSimpleType.mustache create mode 100644 ocaml/sdk-gen/go/templates/ConvertTime.mustache diff --git a/ocaml/sdk-gen/go/templates/ConvertBatch.mustache b/ocaml/sdk-gen/go/templates/ConvertBatch.mustache new file mode 100644 index 00000000000..42f05791cb8 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertBatch.mustache @@ -0,0 +1,20 @@ +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (batch {{type}}, err error) { + rpcStruct, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } +{{#elements}} + {{name_internal}}Value, ok := rpcStruct["{{name}}"] + if ok && {{name_internal}}Value != nil { + batch.{{name_exported}}, err = deserialize{{func_name_suffix}}(fmt.Sprintf("%s.%s", context, "{{name}}"), {{name_internal}}Value) + if err != nil { + return + } + } +{{/elements}} + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertEnum.mustache b/ocaml/sdk-gen/go/templates/ConvertEnum.mustache new file mode 100644 index 00000000000..85bb1660c24 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertEnum.mustache @@ -0,0 +1,25 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, value {{type}}) (string, error) { + _ = context + return string(value), nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (value {{type}}, err error) { + strValue, err := deserializeString(context, input) + if err != nil { + return + } + switch strValue { +{{#items}} + case "{{value}}": + value = {{name}} +{{/items}} + default: + err = fmt.Errorf("unable to parse XenAPI response: got value %q for enum %s at %s, but this is not any of the known values", strValue, "{{type}}", context) + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertFloat.mustache b/ocaml/sdk-gen/go/templates/ConvertFloat.mustache new file mode 100644 index 00000000000..89c5910909d --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertFloat.mustache @@ -0,0 +1,38 @@ +{{#serialize}} +//nolint:unparam +func serialize{{func_name_suffix}}(context string, value {{type}}) (interface{}, error) { + _ = context + if math.IsInf(value, 0) { + if math.IsInf(value, 1) { + return "+Inf", nil + } + return "-Inf", nil + } else if math.IsNaN(value) { + return "NaN", nil + } + return value, nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (value {{type}}, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + value, err = strconv.ParseFloat(strValue, 64) + if err != nil { + switch strValue { + case "+Inf": + return math.Inf(1), nil + case "-Inf": + return math.Inf(-1), nil + case "NaN": + return math.NaN(), nil + } + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertInt.mustache b/ocaml/sdk-gen/go/templates/ConvertInt.mustache new file mode 100644 index 00000000000..dbc7cf37c56 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertInt.mustache @@ -0,0 +1,25 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, value {{type}}) ({{type}}, error) { + _ = context + return value, nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (value {{type}}, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + value, err = strconv.Atoi(strValue) + if err != nil { + floatValue, err1 := strconv.ParseFloat(strValue, 64) + if err1 == nil { + return int(floatValue), nil + } + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertInterface.mustache b/ocaml/sdk-gen/go/templates/ConvertInterface.mustache new file mode 100644 index 00000000000..9090d083058 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertInterface.mustache @@ -0,0 +1,8 @@ +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (inter {{type}}, err error) { + _ = context + inter = input + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertMap.mustache b/ocaml/sdk-gen/go/templates/ConvertMap.mustache new file mode 100644 index 00000000000..b4cca1d7ca8 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertMap.mustache @@ -0,0 +1,43 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, goMap {{type}}) (xenMap map[string]interface{}, err error) { + xenMap = make(map[string]interface{}) + for goKey, goValue := range goMap { + keyContext := fmt.Sprintf("%s[%s]", context, goKey) + xenKey, err := serialize{{key_type}}(keyContext, goKey) + if err != nil { + return xenMap, err + } + xenValue, err := serialize{{value_type}}(keyContext, goValue) + if err != nil { + return xenMap, err + } + xenMap[xenKey] = xenValue + } + return +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (goMap {{type}}, err error) { + xenMap, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } + goMap = make({{type}}, len(xenMap)) + for xenKey, xenValue := range xenMap { + keyContext := fmt.Sprintf("%s[%s]", context, xenKey) + goKey, err := deserialize{{key_type}}(keyContext, xenKey) + if err != nil { + return goMap, err + } + goValue, err := deserialize{{value_type}}(keyContext, xenValue) + if err != nil { + return goMap, err + } + goMap[goKey] = goValue + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertOption.mustache b/ocaml/sdk-gen/go/templates/ConvertOption.mustache new file mode 100644 index 00000000000..ca49dea9f3b --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertOption.mustache @@ -0,0 +1,27 @@ +{{#serialize}} +func serializeOption{{func_name_suffix}}(context string, input Option{{func_name_suffix}}) (option interface{}, err error) { + if input == nil { + return + } + option, err = serialize{{func_name_suffix}}(context, *input) + if err != nil { + return + } + return +} + +{{/serialize}} +{{#deserialize}} +func deserializeOption{{func_name_suffix}}(context string, input interface{}) (option Option{{func_name_suffix}}, err error) { + if input == nil { + return + } + value, err := deserialize{{func_name_suffix}}(context, input) + if err != nil { + return + } + option = &value + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertRecord.mustache b/ocaml/sdk-gen/go/templates/ConvertRecord.mustache new file mode 100644 index 00000000000..6e973b87dd0 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertRecord.mustache @@ -0,0 +1,52 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, record {{type}}) (rpcStruct map[string]interface{}, err error) { + rpcStruct = map[string]interface{}{} +{{#fields}} +{{#type_option}} + {{name_internal}}, err := serializeOption{{func_name_suffix}}(fmt.Sprintf("%s.%s", context, "{{name}}"), record.{{name_exported}}) + if err != nil { + return + } + if {{name_internal}} != nil { + rpcStruct["{{name}}"] = {{name_internal}} + } +{{/type_option}} +{{^type_option}} + rpcStruct["{{name}}"], err = serialize{{func_name_suffix}}(fmt.Sprintf("%s.%s", context, "{{name}}"), record.{{name_exported}}) + if err != nil { + return + } +{{/type_option}} +{{/fields}} + return +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (record {{type}}, err error) { + rpcStruct, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } +{{#fields}} +{{#type_option}} + record.{{name_exported}}, err = deserializeOption{{func_name_suffix}}(fmt.Sprintf("%s.%s", context, "{{name}}"), rpcStruct["{{name}}"]) + if err != nil { + return + } +{{/type_option}} +{{^type_option}} + {{name_internal}}Value, ok := rpcStruct["{{name}}"] + if ok && {{name_internal}}Value != nil { + record.{{name_exported}}, err = deserialize{{func_name_suffix}}(fmt.Sprintf("%s.%s", context, "{{name}}"), {{name_internal}}Value) + if err != nil { + return + } + } +{{/type_option}} +{{/fields}} + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertRef.mustache b/ocaml/sdk-gen/go/templates/ConvertRef.mustache new file mode 100644 index 00000000000..2b938dcb658 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertRef.mustache @@ -0,0 +1,18 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, ref {{type}}) (string, error) { + _ = context + return string(ref), nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) ({{type}}, error) { + var ref {{type}} + value, ok := input.(string) + if !ok { + return ref, fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "string", context, reflect.TypeOf(input), input) + } + return {{type}}(value), nil +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertSet.mustache b/ocaml/sdk-gen/go/templates/ConvertSet.mustache new file mode 100644 index 00000000000..c3f37099a78 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertSet.mustache @@ -0,0 +1,35 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, slice []{{type}}) (set []interface{}, err error) { + set = make([]interface{}, len(slice)) + for index, item := range slice { + itemContext := fmt.Sprintf("%s[%d]", context, index) + itemValue, err := serialize{{item_func_suffix}}(itemContext, item) + if err != nil { + return set, err + } + set[index] = itemValue + } + return +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (slice []{{type}}, err error) { + set, ok := input.([]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "[]interface{}", context, reflect.TypeOf(input), input) + return + } + slice = make([]{{type}}, len(set)) + for index, item := range set { + itemContext := fmt.Sprintf("%s[%d]", context, index) + itemValue, err := deserialize{{item_func_suffix}}(itemContext, item) + if err != nil { + return slice, err + } + slice[index] = itemValue + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertSimpleType.mustache b/ocaml/sdk-gen/go/templates/ConvertSimpleType.mustache new file mode 100644 index 00000000000..552052932a6 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertSimpleType.mustache @@ -0,0 +1,20 @@ +{{#serialize}} +func serialize{{func_name_suffix}}(context string, value {{type}}) ({{type}}, error) { + _ = context + return value, nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (value {{type}}, err error) { + if input == nil { + return + } + value, ok := input.({{type}}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "{{type}}", context, reflect.TypeOf(input), input) + } + return +} + +{{/deserialize}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/ConvertTime.mustache b/ocaml/sdk-gen/go/templates/ConvertTime.mustache new file mode 100644 index 00000000000..d79f65841ad --- /dev/null +++ b/ocaml/sdk-gen/go/templates/ConvertTime.mustache @@ -0,0 +1,34 @@ +{{#serialize}} +var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"} + +//nolint:unparam +func serialize{{func_name_suffix}}(context string, value {{type}}) (string, error) { + _ = context + return value.Format(time.RFC3339), nil +} + +{{/serialize}} +{{#deserialize}} +func deserialize{{func_name_suffix}}(context string, input interface{}) (value {{type}}, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + floatValue, err := strconv.ParseFloat(strValue, 64) + if err != nil { + for _, timeFormat := range timeFormats { + value, err = time.Parse(timeFormat, strValue) + if err == nil { + return value, nil + } + } + return + } + unixTimestamp, err := strconv.ParseInt(strconv.Itoa(int(floatValue)), 10, 64) + value = time.Unix(unixTimestamp, 0) + + return +} + +{{/deserialize}} \ No newline at end of file From e6c911caf89afa00099d288d79cdafbd216a27d1 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 14:20:01 +0800 Subject: [PATCH 02/12] CP-47358: Generate convert functions Go code Signed-off-by: Luca Zhang --- ocaml/sdk-gen/common/CommonFunctions.ml | 53 ++++ ocaml/sdk-gen/common/CommonFunctions.mli | 8 + ocaml/sdk-gen/go/gen_go_binding.ml | 49 ++++ ocaml/sdk-gen/go/gen_go_helper.ml | 305 ++++++++++++++++++++++- ocaml/sdk-gen/go/gen_go_helper.mli | 69 +++++ 5 files changed, 482 insertions(+), 2 deletions(-) diff --git a/ocaml/sdk-gen/common/CommonFunctions.ml b/ocaml/sdk-gen/common/CommonFunctions.ml index 2c29a3cbd91..989448f49e2 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.ml +++ b/ocaml/sdk-gen/common/CommonFunctions.ml @@ -355,3 +355,56 @@ let objects = api in objects_of_api api + +module TypesOfMessages = struct + open Xapi_stdext_std + + let records = + List.map + (fun obj -> + let obj_name = String.lowercase_ascii obj.name in + (obj_name, Datamodel_utils.fields_of_obj obj) + ) + objects + + let rec decompose = function + | Set x as y -> + y :: decompose x + | Map (a, b) as y -> + (y :: decompose a) @ decompose b + | Option x as y -> + y :: decompose x + | Record r as y -> + let name = String.lowercase_ascii r in + let types_in_field = + List.assoc_opt name records + |> Option.value ~default:[] + |> List.concat_map (fun field -> decompose field.ty) + in + y :: types_in_field + | (SecretString | String | Int | Float | DateTime | Enum _ | Bool | Ref _) + as x -> + [x] + + let mesages objects = objects |> List.concat_map (fun x -> x.messages) + + (** All types of params in a list of objects (automatically decomposes) *) + let of_params objects = + let param_types = + mesages objects + |> List.concat_map (fun x -> x.msg_params) + |> List.map (fun p -> p.param_type) + |> Listext.List.setify + in + List.concat_map decompose param_types |> Listext.List.setify + + (** All types of results in a list of objects (automatically decomposes) *) + let of_results objects = + let return_types = + let aux accu msg = + match msg.msg_result with None -> accu | Some (ty, _) -> ty :: accu + in + mesages objects |> List.fold_left aux [] |> Listext.List.setify + in + List.concat_map decompose return_types |> Listext.List.setify +end diff --git a/ocaml/sdk-gen/common/CommonFunctions.mli b/ocaml/sdk-gen/common/CommonFunctions.mli index 197dcb8a3a4..71106ad1960 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.mli +++ b/ocaml/sdk-gen/common/CommonFunctions.mli @@ -137,3 +137,11 @@ val session_id : param val objects : obj list (** Objects of api that generate SDKs. *) + +module TypesOfMessages : sig + val of_params : Datamodel_types.obj list -> Datamodel_types.ty list + (** All the types in the params of messages*) + + val of_results : Datamodel_types.obj list -> Datamodel_types.ty list + (** All the types in the results of messages*) +end diff --git a/ocaml/sdk-gen/go/gen_go_binding.ml b/ocaml/sdk-gen/go/gen_go_binding.ml index 985bc671443..7e7a6ad726f 100644 --- a/ocaml/sdk-gen/go/gen_go_binding.ml +++ b/ocaml/sdk-gen/go/gen_go_binding.ml @@ -44,10 +44,59 @@ let render_api_messages_and_errors destdir = generate_file ~rendered:messages_rendered ~destdir ~output_file:"api_messages.go" +let render_convert_header () = + let name s = `O [("name", `String s); ("sname", `Null)] in + let obj = + `O + [ + ("name", `String "convert") + ; ( "modules" + , `O + [ + ("import", `Bool true) + ; ( "items" + , `A (List.map name ["fmt"; "math"; "reflect"; "strconv"; "time"]) + ) + ] + ) + ] + in + render_template "FileHeader.mustache" obj ~newline:true () + +let render_converts destdir = + let event = render_template "ConvertBatch.mustache" Convert.event_batch () in + let interface = + render_template "ConvertInterface.mustache" Convert.interface () + in + let param_types = TypesOfMessages.of_params objects in + let result_types = TypesOfMessages.of_results objects in + let generate types of_json = + types + |> List.map (fun ty -> + let params = Convert.of_ty ty in + let template = Convert.template_of_convert params in + let json : Mustache.Json.t = of_json params in + render_template template json () + ) + |> String.concat "" + in + let rendered = + let serializes_rendered = generate param_types Convert.of_serialize in + let deserializes_rendered = generate result_types Convert.of_deserialize in + render_convert_header () + ^ serializes_rendered + ^ deserializes_rendered + ^ event + ^ String.trim interface + ^ "\n" + in + generate_file ~rendered ~destdir ~output_file:"convert.go" + let main destdir = render_api_messages_and_errors destdir ; let enums = Json.all_enums objects in render_enums enums destdir ; + render_converts destdir ; let objects = Json.xenapi objects in List.iter (fun (name, obj) -> diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 5412f0bc564..92f2bf97dd9 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -15,7 +15,6 @@ open Datamodel_types open CommonFunctions -module Types = Datamodel_utils.Types let templates_dir = "templates" @@ -28,6 +27,14 @@ let snake_to_camel (s : string) : string = |> List.map String.capitalize_ascii |> String.concat "" +let records = + List.map + (fun obj -> + let obj_name = snake_to_camel obj.name ^ "Record" in + (obj_name, Datamodel_utils.fields_of_obj obj) + ) + objects + let render_template template_file json ?(newline = false) () = let templ = string_of_file (templates_dir // template_file) |> Mustache.of_string @@ -53,6 +60,33 @@ module Json = struct let merge_maps m maps = List.fold_left (fun acc map -> StringMap.union choose_enum acc map) m maps + let rec func_name_suffix ty = + match ty with + | SecretString | String -> + "String" + | Int -> + "Int" + | Float -> + "Float" + | Bool -> + "Bool" + | DateTime -> + "Time" + | Enum (name, _) -> + "Enum" ^ snake_to_camel name + | Set ty -> + func_name_suffix ty ^ "Set" + | Map (ty1, ty2) -> + let k_suffix = func_name_suffix ty1 in + let v_suffix = func_name_suffix ty2 in + k_suffix ^ "To" ^ v_suffix ^ "Map" + | Ref r -> + snake_to_camel r ^ "Ref" + | Record r -> + snake_to_camel r ^ "Record" + | Option ty -> + func_name_suffix ty + let rec string_of_ty_with_enums ty : string * enums = match ty with | SecretString | String -> @@ -130,7 +164,7 @@ module Json = struct let all_enums objs = let enums = - Types.of_objects objs + Datamodel_utils.Types.of_objects objs |> List.map (fun ty -> let _, e = string_of_ty_with_enums ty in e @@ -198,3 +232,270 @@ module Json = struct let api_errors = List.map (fun error -> `O [("name", `String error)]) !Api_errors.errors end + +module Convert = struct + type params = {func_suffix: string; value_ty: string} + + type params_of_option = {func_suffix: string} + + type params_of_set = { + func_suffix: string + ; value_ty: string + ; item_fp_type: string + } + + type params_of_record_field = { + name: string + ; name_internal: string + ; name_exported: string + ; func_suffix: string + ; type_option: bool + } + + type params_of_record = { + func_suffix: string + ; value_ty: string + ; fields: params_of_record_field list + } + + type params_of_enum_item = {value: string; name: string} + + type params_of_enum = { + func_suffix: string + ; value_ty: string + ; items: params_of_enum_item list + } + + type params_of_map = { + func_suffix: string + ; value_ty: string + ; key_ty: string + ; val_ty: string + } + + type convert_params = + | Simple of params + | Int of params + | Float of params + | Time of params + | Ref of params + | Option of params_of_option + | Set of params_of_set + | Enum of params_of_enum + | Record of params_of_record + | Map of params_of_map + + let template_of_convert : convert_params -> string = function + | Simple _ -> + "ConvertSimpleType.mustache" + | Int _ -> + "ConvertInt.mustache" + | Float _ -> + "ConvertFloat.mustache" + | Time _ -> + "ConvertTime.mustache" + | Ref _ -> + "ConvertRef.mustache" + | Set _ -> + "ConvertSet.mustache" + | Record _ -> + "ConvertRecord.mustache" + | Map _ -> + "ConvertMap.mustache" + | Enum _ -> + "ConvertEnum.mustache" + | Option _ -> + "ConvertOption.mustache" + + let to_json : convert_params -> Mustache.Json.value = function + | Simple params | Int params | Float params | Time params | Ref params -> + `O + [ + ("func_name_suffix", `String params.func_suffix) + ; ("type", `String params.value_ty) + ] + | Option params -> + `O [("func_name_suffix", `String params.func_suffix)] + | Set params -> + `O + [ + ("func_name_suffix", `String params.func_suffix) + ; ("type", `String params.value_ty) + ; ("item_func_suffix", `String params.item_fp_type) + ] + | Record params -> + let fields = + List.rev_map + (fun (field : params_of_record_field) -> + `O + [ + ("name", `String field.name) + ; ("name_internal", `String field.name_internal) + ; ("name_exported", `String field.name_exported) + ; ("func_name_suffix", `String field.func_suffix) + ; ("type_option", `Bool field.type_option) + ] + ) + params.fields + in + `O + [ + ("func_name_suffix", `String params.func_suffix) + ; ("type", `String params.value_ty) + ; ("fields", `A fields) + ] + | Enum params -> + let of_value item = + `O [("value", `String item.value); ("name", `String item.name)] + in + `O + [ + ("type", `String params.value_ty) + ; ("func_name_suffix", `String params.func_suffix) + ; ("items", `A (List.map of_value params.items)) + ] + | Map params -> + `O + [ + ("func_name_suffix", `String params.func_suffix) + ; ("type", `String params.value_ty) + ; ("key_type", `String params.key_ty) + ; ("value_type", `String params.val_ty) + ] + + let fields record_name = + let fields = + List.assoc_opt record_name records + |> Option.value ~default:[] + |> List.rev_map (fun field -> + ( String.concat "_" field.full_name + , Json.func_name_suffix field.ty + , match field.ty with Option _ -> true | _ -> false + ) + ) + in + if record_name = "EventRecord" then + ("snapshot", "RecordInterface", false) :: fields + else + fields + + let of_ty = function + | SecretString | String -> + Simple {func_suffix= "String"; value_ty= "string"} + | Int -> + Int {func_suffix= "Int"; value_ty= "int"} + | Float -> + Float {func_suffix= "Float"; value_ty= "float64"} + | Bool -> + Simple {func_suffix= "Bool"; value_ty= "bool"} + | DateTime -> + Time {func_suffix= "Time"; value_ty= "time.Time"} + | Enum (name, kv) as ty -> + let name = snake_to_camel name in + let items = + List.map (fun (k, _) -> {value= k; name= name ^ snake_to_camel k}) kv + in + Enum {func_suffix= Json.func_name_suffix ty; value_ty= name; items} + | Set ty as set -> + let fp_ty = Json.func_name_suffix ty in + let ty, _ = Json.string_of_ty_with_enums ty in + Set + { + func_suffix= Json.func_name_suffix set + ; value_ty= ty + ; item_fp_type= fp_ty + } + | Map (ty1, ty2) as ty -> + let name, _ = Json.string_of_ty_with_enums ty in + Map + { + func_suffix= Json.func_name_suffix ty + ; value_ty= name + ; key_ty= Json.func_name_suffix ty1 + ; val_ty= Json.func_name_suffix ty2 + } + | Ref _ as ty -> + let name = Json.func_name_suffix ty in + Ref {func_suffix= name; value_ty= name} + | Record r -> + let name = snake_to_camel r ^ "Record" in + let fields = + List.map + (fun (name, func_suffix, is_option_type) -> + let camel_name = snake_to_camel name in + { + name + ; name_internal= String.uncapitalize_ascii camel_name + ; name_exported= camel_name + ; func_suffix + ; type_option= is_option_type + } + ) + (fields name) + in + Record {func_suffix= name; value_ty= name; fields} + | Option ty -> + Option {func_suffix= Json.func_name_suffix ty} + + let of_serialize params = + `O [("serialize", `A [to_json params]); ("deserialize", `Null)] + + let of_deserialize params = + `O [("serialize", `Null); ("deserialize", `A [to_json params])] + + let event_batch : Mustache.Json.t = + `O + [ + ( "deserialize" + , `A + [ + `O + [ + ("func_name_suffix", `String "EventBatch") + ; ("type", `String "EventBatch") + ; ( "elements" + , `A + [ + `O + [ + ("name", `String "token") + ; ("name_internal", `String "token") + ; ("name_exported", `String "Token") + ; ("func_name_suffix", `String "String") + ] + ; `O + [ + ("name", `String "validRefCounts") + ; ("name_internal", `String "validRefCounts") + ; ("name_exported", `String "ValidRefCounts") + ; ("func_name_suffix", `String "StringToIntMap") + ] + ; `O + [ + ("name", `String "events") + ; ("name_internal", `String "events") + ; ("name_exported", `String "Events") + ; ("func_name_suffix", `String "EventRecordSet") + ] + ] + ) + ] + ] + ) + ] + + let interface : Mustache.Json.t = + `O + [ + ( "deserialize" + , `A + [ + `O + [ + ("func_name_suffix", `String "RecordInterface") + ; ("type", `String "RecordInterface") + ] + ] + ) + ] +end diff --git a/ocaml/sdk-gen/go/gen_go_helper.mli b/ocaml/sdk-gen/go/gen_go_helper.mli index f6c7643e071..29a301b965a 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.mli +++ b/ocaml/sdk-gen/go/gen_go_helper.mli @@ -30,3 +30,72 @@ module Json : sig val api_errors : Mustache.Json.value list end + +module Convert : sig + type params = {func_suffix: string; value_ty: string} + + type params_of_option = {func_suffix: string} + + type params_of_set = { + func_suffix: string + ; value_ty: string + ; item_fp_type: string + } + + type params_of_record_field = { + name: string + ; name_internal: string + ; name_exported: string + ; func_suffix: string + ; type_option: bool + } + + type params_of_record = { + func_suffix: string + ; value_ty: string + ; fields: params_of_record_field list + } + + type params_of_enum_item = {value: string; name: string} + + type params_of_enum = { + func_suffix: string + ; value_ty: string + ; items: params_of_enum_item list + } + + type params_of_map = { + func_suffix: string + ; value_ty: string + ; key_ty: string + ; val_ty: string + } + + type convert_params = + | Simple of params + | Int of params + | Float of params + | Time of params + | Ref of params + | Option of params_of_option + | Set of params_of_set + | Enum of params_of_enum + | Record of params_of_record + | Map of params_of_map + + val template_of_convert : convert_params -> string + + val to_json : convert_params -> Mustache.Json.value + + val fields : string -> (string * string * bool) list + + val of_ty : Datamodel_types.ty -> convert_params + + val of_serialize : convert_params -> Mustache.Json.t + + val of_deserialize : convert_params -> Mustache.Json.t + + val event_batch : Mustache.Json.t + + val interface : Mustache.Json.t +end From 438c36be0f151a9156c41b8b89836c11d627d94c Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 20:10:07 +0800 Subject: [PATCH 03/12] CP-47358: Add unit tests for generating convert functions Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/test_data/batch_convert.go | 29 ++ ocaml/sdk-gen/go/test_data/enum_convert.go | 20 + ocaml/sdk-gen/go/test_data/float_convert.go | 33 ++ ocaml/sdk-gen/go/test_data/int_convert.go | 20 + .../sdk-gen/go/test_data/interface_convert.go | 5 + ocaml/sdk-gen/go/test_data/map_convert.go | 38 ++ ocaml/sdk-gen/go/test_data/option_convert.go | 22 + ocaml/sdk-gen/go/test_data/record_convert.go | 35 ++ ocaml/sdk-gen/go/test_data/ref_convert.go | 13 + ocaml/sdk-gen/go/test_data/set_convert.go | 30 ++ .../go/test_data/simple_type_convert.go | 31 ++ ocaml/sdk-gen/go/test_data/time_convert.go | 29 ++ ocaml/sdk-gen/go/test_gen_go.ml | 375 ++++++++++++++++++ 13 files changed, 680 insertions(+) create mode 100644 ocaml/sdk-gen/go/test_data/batch_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/enum_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/float_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/int_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/interface_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/map_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/option_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/record_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/ref_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/set_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/simple_type_convert.go create mode 100644 ocaml/sdk-gen/go/test_data/time_convert.go diff --git a/ocaml/sdk-gen/go/test_data/batch_convert.go b/ocaml/sdk-gen/go/test_data/batch_convert.go new file mode 100644 index 00000000000..fd0e70607f6 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/batch_convert.go @@ -0,0 +1,29 @@ +func deserializeEventBatch(context string, input interface{}) (batch EventBatch, err error) { + rpcStruct, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } + tokenValue, ok := rpcStruct["token"] + if ok && tokenValue != nil { + batch.Token, err = deserializeString(fmt.Sprintf("%s.%s", context, "token"), tokenValue) + if err != nil { + return + } + } + validRefCountsValue, ok := rpcStruct["validRefCounts"] + if ok && validRefCountsValue != nil { + batch.ValidRefCounts, err = deserializeStringToIntMap(fmt.Sprintf("%s.%s", context, "validRefCounts"), validRefCountsValue) + if err != nil { + return + } + } + eventsValue, ok := rpcStruct["events"] + if ok && eventsValue != nil { + batch.Events, err = deserializeEventRecordSet(fmt.Sprintf("%s.%s", context, "events"), eventsValue) + if err != nil { + return + } + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/enum_convert.go b/ocaml/sdk-gen/go/test_data/enum_convert.go new file mode 100644 index 00000000000..40129c0e5ca --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/enum_convert.go @@ -0,0 +1,20 @@ +func serializeEnumTaskStatusType(context string, value TaskStatusType) (string, error) { + _ = context + return string(value), nil +} + +func deserializeEnumTaskStatusType(context string, input interface{}) (value TaskStatusType, err error) { + strValue, err := deserializeString(context, input) + if err != nil { + return + } + switch strValue { + case "pending": + value = TaskStatusTypePending + case "success": + value = TaskStatusTypeSuccess + default: + err = fmt.Errorf("unable to parse XenAPI response: got value %q for enum %s at %s, but this is not any of the known values", strValue, "TaskStatusType", context) + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/float_convert.go b/ocaml/sdk-gen/go/test_data/float_convert.go new file mode 100644 index 00000000000..736ad6f8111 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/float_convert.go @@ -0,0 +1,33 @@ +//nolint:unparam +func serializeFloat(context string, value float64) (interface{}, error) { + _ = context + if math.IsInf(value, 0) { + if math.IsInf(value, 1) { + return "+Inf", nil + } + return "-Inf", nil + } else if math.IsNaN(value) { + return "NaN", nil + } + return value, nil +} + +func deserializeFloat(context string, input interface{}) (value float64, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + value, err = strconv.ParseFloat(strValue, 64) + if err != nil { + switch strValue { + case "+Inf": + return math.Inf(1), nil + case "-Inf": + return math.Inf(-1), nil + case "NaN": + return math.NaN(), nil + } + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/int_convert.go b/ocaml/sdk-gen/go/test_data/int_convert.go new file mode 100644 index 00000000000..0688dffa600 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/int_convert.go @@ -0,0 +1,20 @@ +func serializeInt(context string, value int) (int, error) { + _ = context + return value, nil +} + +func deserializeInt(context string, input interface{}) (value int, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + value, err = strconv.Atoi(strValue) + if err != nil { + floatValue, err1 := strconv.ParseFloat(strValue, 64) + if err1 == nil { + return int(floatValue), nil + } + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/interface_convert.go b/ocaml/sdk-gen/go/test_data/interface_convert.go new file mode 100644 index 00000000000..fec2c91d133 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/interface_convert.go @@ -0,0 +1,5 @@ +func deserializeRecordInterface(context string, input interface{}) (inter RecordInterface, err error) { + _ = context + inter = input + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/map_convert.go b/ocaml/sdk-gen/go/test_data/map_convert.go new file mode 100644 index 00000000000..fff680ebc35 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/map_convert.go @@ -0,0 +1,38 @@ +func serializeVIFRefToStringMap(context string, goMap map[VIFRef]string) (xenMap map[string]interface{}, err error) { + xenMap = make(map[string]interface{}) + for goKey, goValue := range goMap { + keyContext := fmt.Sprintf("%s[%s]", context, goKey) + xenKey, err := serializeVIFRef(keyContext, goKey) + if err != nil { + return xenMap, err + } + xenValue, err := serializeString(keyContext, goValue) + if err != nil { + return xenMap, err + } + xenMap[xenKey] = xenValue + } + return +} + +func deserializePBDRefToPBDRecordMap(context string, input interface{}) (goMap map[PBDRef]PBDRecord, err error) { + xenMap, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } + goMap = make(map[PBDRef]PBDRecord, len(xenMap)) + for xenKey, xenValue := range xenMap { + keyContext := fmt.Sprintf("%s[%s]", context, xenKey) + goKey, err := deserializePBDRef(keyContext, xenKey) + if err != nil { + return goMap, err + } + goValue, err := deserializePBDRecord(keyContext, xenValue) + if err != nil { + return goMap, err + } + goMap[goKey] = goValue + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/option_convert.go b/ocaml/sdk-gen/go/test_data/option_convert.go new file mode 100644 index 00000000000..4a5ce03a70b --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/option_convert.go @@ -0,0 +1,22 @@ +func serializeOptionSrStatRecord(context string, input OptionSrStatRecord) (option interface{}, err error) { + if input == nil { + return + } + option, err = serializeSrStatRecord(context, *input) + if err != nil { + return + } + return +} + +func deserializeOptionSrStatRecord(context string, input interface{}) (option OptionSrStatRecord, err error) { + if input == nil { + return + } + value, err := deserializeSrStatRecord(context, input) + if err != nil { + return + } + option = &value + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/record_convert.go b/ocaml/sdk-gen/go/test_data/record_convert.go new file mode 100644 index 00000000000..55a8e9c3c90 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/record_convert.go @@ -0,0 +1,35 @@ +func serializeVBDRecord(context string, record VBDRecord) (rpcStruct map[string]interface{}, err error) { + rpcStruct = map[string]interface{}{} + rpcStruct["uuid"], err = serializeString(fmt.Sprintf("%s.%s", context, "uuid"), record.UUID) + if err != nil { + return + } + rpcStruct["allowed_operations"], err = serializeEnumVbdOperationsSet(fmt.Sprintf("%s.%s", context, "allowed_operations"), record.AllowedOperations) + if err != nil { + return + } + return +} + +func deserializeVBDRecord(context string, input interface{}) (record VBDRecord, err error) { + rpcStruct, ok := input.(map[string]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "map[string]interface{}", context, reflect.TypeOf(input), input) + return + } + uuidValue, ok := rpcStruct["uuid"] + if ok && uuidValue != nil { + record.UUID, err = deserializeString(fmt.Sprintf("%s.%s", context, "uuid"), uuidValue) + if err != nil { + return + } + } + allowedOperationsValue, ok := rpcStruct["allowed_operations"] + if ok && allowedOperationsValue != nil { + record.AllowedOperations, err = deserializeEnumVbdOperationsSet(fmt.Sprintf("%s.%s", context, "allowed_operations"), allowedOperationsValue) + if err != nil { + return + } + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/ref_convert.go b/ocaml/sdk-gen/go/test_data/ref_convert.go new file mode 100644 index 00000000000..dc23fc88ffa --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/ref_convert.go @@ -0,0 +1,13 @@ +func serializeVMRef(context string, ref VMRef) (string, error) { + _ = context + return string(ref), nil +} + +func deserializeVMRef(context string, input interface{}) (VMRef, error) { + var ref VMRef + value, ok := input.(string) + if !ok { + return ref, fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "string", context, reflect.TypeOf(input), input) + } + return VMRef(value), nil +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/set_convert.go b/ocaml/sdk-gen/go/test_data/set_convert.go new file mode 100644 index 00000000000..1d8adb73764 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/set_convert.go @@ -0,0 +1,30 @@ +func serializeSRRefSet(context string, slice []SRRef) (set []interface{}, err error) { + set = make([]interface{}, len(slice)) + for index, item := range slice { + itemContext := fmt.Sprintf("%s[%d]", context, index) + itemValue, err := serializeSRRef(itemContext, item) + if err != nil { + return set, err + } + set[index] = itemValue + } + return +} + +func deserializeStringSet(context string, input interface{}) (slice []string, err error) { + set, ok := input.([]interface{}) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "[]interface{}", context, reflect.TypeOf(input), input) + return + } + slice = make([]string, len(set)) + for index, item := range set { + itemContext := fmt.Sprintf("%s[%d]", context, index) + itemValue, err := deserializeString(itemContext, item) + if err != nil { + return slice, err + } + slice[index] = itemValue + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/simple_type_convert.go b/ocaml/sdk-gen/go/test_data/simple_type_convert.go new file mode 100644 index 00000000000..5b482e0a5b7 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/simple_type_convert.go @@ -0,0 +1,31 @@ +func serializeString(context string, value string) (string, error) { + _ = context + return value, nil +} + +func serializeBool(context string, value bool) (bool, error) { + _ = context + return value, nil +} + +func deserializeString(context string, input interface{}) (value string, err error) { + if input == nil { + return + } + value, ok := input.(string) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "string", context, reflect.TypeOf(input), input) + } + return +} + +func deserializeBool(context string, input interface{}) (value bool, err error) { + if input == nil { + return + } + value, ok := input.(bool) + if !ok { + err = fmt.Errorf("failed to parse XenAPI response: expected Go type %s at %s but got Go type %s with value %v", "bool", context, reflect.TypeOf(input), input) + } + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/time_convert.go b/ocaml/sdk-gen/go/test_data/time_convert.go new file mode 100644 index 00000000000..d6da10f4d42 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/time_convert.go @@ -0,0 +1,29 @@ +var timeFormats = []string{time.RFC3339, "20060102T15:04:05Z", "20060102T15:04:05"} + +//nolint:unparam +func serializeTime(context string, value time.Time) (string, error) { + _ = context + return value.Format(time.RFC3339), nil +} + +func deserializeTime(context string, input interface{}) (value time.Time, err error) { + _ = context + if input == nil { + return + } + strValue := fmt.Sprintf("%v", input) + floatValue, err := strconv.ParseFloat(strValue, 64) + if err != nil { + for _, timeFormat := range timeFormats { + value, err = time.Parse(timeFormat, strValue) + if err == nil { + return value, nil + } + } + return + } + unixTimestamp, err := strconv.ParseInt(strconv.Itoa(int(floatValue)), 10, 64) + value = time.Unix(unixTimestamp, 0) + + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 37a6e037d63..31964f7751e 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -168,6 +168,132 @@ let verify_msgs_or_errors lst = in List.for_all verify_msg_or_error lst +let verify_simple_convert_member = function + | "func_name_suffix", `String _ | "type", `String _ -> + true + | _ -> + false + +let verify_simple_convert_keys = ["func_name_suffix"; "type"] + +let verify_simple_convert = function + | `O items -> + schema_check verify_simple_convert_keys verify_simple_convert_member items + | _ -> + false + +let verify_option_convert_member = function + | "func_name_suffix", `String _ -> + true + | _ -> + false + +let option_convert_keys = ["func_name_suffix"] + +let verify_option_convert = function + | `O items -> + schema_check option_convert_keys verify_option_convert_member items + | _ -> + false + +let verify_set_convert_member = function + | "func_name_suffix", `String _ + | "type", `String _ + | "item_func_suffix", `String _ -> + true + | _ -> + false + +let convert_set_keys = ["func_name_suffix"; "type"; "item_func_suffix"] + +let verify_set_convert = function + | `O items -> + schema_check convert_set_keys verify_set_convert_member items + | _ -> + false + +let record_field_keys = + ["name"; "name_internal"; "name_exported"; "func_name_suffix"; "type_option"] + +let verify_record_field_member = function + | "name", `String _ + | "name_internal", `String _ + | "func_name_suffix", `String _ + | "type_option", `Bool _ + | "name_exported", `String _ -> + true + | _ -> + false + +let verify_record_field = function + | `O items -> + schema_check record_field_keys verify_record_field_member items + | _ -> + false + +let verify_record_convert_member = function + | "func_name_suffix", `String _ | "type", `String _ -> + true + | "fields", `A fields -> + List.for_all verify_record_field fields + | _ -> + false + +let convert_record_keys = ["func_name_suffix"; "type"; "fields"] + +let verify_record_convert = function + | `O items -> + schema_check convert_record_keys verify_record_convert_member items + | _ -> + false + +let enum_item_keys = ["value"; "name"] + +let verify_enum_item_member = function + | "name", `String _ | "value", `String _ -> + true + | _ -> + false + +let verify_enum_item = function + | `O members -> + schema_check enum_item_keys verify_enum_item_member members + | _ -> + false + +let enum_convert_keys = ["func_name_suffix"; "type"; "items"] + +let verify_enum_convert_member = function + | "func_name_suffix", `String _ | "type", `String _ -> + true + | "items", `A items -> + List.for_all verify_enum_item items + | _ -> + false + +let verify_enum_convert = function + | `O items -> + schema_check enum_convert_keys verify_enum_convert_member items + | _ -> + false + +let map_convert_keys = ["func_name_suffix"; "type"; "key_type"; "value_type"] + +let verify_map_convert_member = function + | "type", `String _ + | "key_type", `String _ + | "func_name_suffix", `String _ + | "value_type", `String _ -> + true + | _ -> + false + +let verify_map_convert = function + | `O items -> + schema_check map_convert_keys verify_map_convert_member items + | _ -> + false + let rec string_of_json_value (value : Mustache.Json.value) : string = match value with | `Null -> @@ -303,6 +429,151 @@ let api_messages : Mustache.Json.t = ) ] +let simple_type_convert : Mustache.Json.t = + let array = + [ + `O [("func_name_suffix", `String "String"); ("type", `String "string")] + ; `O [("func_name_suffix", `String "Bool"); ("type", `String "bool")] + ] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let int_convert : Mustache.Json.t = + let array = + [`O [("func_name_suffix", `String "Int"); ("type", `String "int")]] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let float_convert : Mustache.Json.t = + let array = + [`O [("func_name_suffix", `String "Float"); ("type", `String "float64")]] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let time_convert : Mustache.Json.t = + let array = + [`O [("func_name_suffix", `String "Time"); ("type", `String "time.Time")]] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let ref_string_convert : Mustache.Json.t = + let array = + [`O [("func_name_suffix", `String "VMRef"); ("type", `String "VMRef")]] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let set_convert : Mustache.Json.t = + let serialize = + [ + `O + [ + ("func_name_suffix", `String "SRRefSet") + ; ("type", `String "SRRef") + ; ("item_func_suffix", `String "SRRef") + ] + ] + in + let deserialize = + [ + `O + [ + ("func_name_suffix", `String "StringSet") + ; ("type", `String "string") + ; ("item_func_suffix", `String "String") + ] + ] + in + `O [("serialize", `A serialize); ("deserialize", `A deserialize)] + +let record_convert : Mustache.Json.t = + let array = + [ + `O + [ + ("func_name_suffix", `String "VBDRecord") + ; ("type", `String "VBDRecord") + ; ( "fields" + , `A + [ + `O + [ + ("name", `String "uuid") + ; ("name_internal", `String "uuid") + ; ("name_exported", `String "UUID") + ; ("func_name_suffix", `String "String") + ; ("type_option", `Bool false) + ] + ; `O + [ + ("name", `String "allowed_operations") + ; ("name_internal", `String "allowedOperations") + ; ("name_exported", `String "AllowedOperations") + ; ("func_name_suffix", `String "EnumVbdOperationsSet") + ; ("type_option", `Bool false) + ] + ] + ) + ] + ] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let map_convert : Mustache.Json.t = + let deserialize = + [ + `O + [ + ("func_name_suffix", `String "PBDRefToPBDRecordMap") + ; ("type", `String "map[PBDRef]PBDRecord") + ; ("key_type", `String "PBDRef") + ; ("value_type", `String "PBDRecord") + ] + ] + in + let serialize = + [ + `O + [ + ("func_name_suffix", `String "VIFRefToStringMap") + ; ("type", `String "map[VIFRef]string") + ; ("key_type", `String "VIFRef") + ; ("value_type", `String "String") + ] + ] + in + `O [("serialize", `A serialize); ("deserialize", `A deserialize)] + +let enum_convert : Mustache.Json.t = + let array = + [ + `O + [ + ("func_name_suffix", `String "EnumTaskStatusType") + ; ("type", `String "TaskStatusType") + ; ( "items" + , `A + [ + `O + [ + ("name", `String "TaskStatusTypePending") + ; ("value", `String "pending") + ] + ; `O + [ + ("name", `String "TaskStatusTypeSuccess") + ; ("value", `String "success") + ] + ] + ) + ] + ] + in + `O [("serialize", `A array); ("deserialize", `A array)] + +let option_convert : Mustache.Json.t = + let array = [`O [("func_name_suffix", `String "SrStatRecord")]] in + `O [("serialize", `A array); ("deserialize", `A array)] + module TemplatesTest = Generic.MakeStateless (struct module Io = struct type input_t = string * Mustache.Json.t @@ -328,6 +599,30 @@ module TemplatesTest = Generic.MakeStateless (struct let api_messages_rendered = string_of_file "api_messages.go" + let simple_type_rendered = string_of_file "simple_type_convert.go" + + let int_convert_rendered = string_of_file "int_convert.go" + + let float_convert_rendered = string_of_file "float_convert.go" + + let time_convert_rendered = string_of_file "time_convert.go" + + let string_ref_rendered = string_of_file "ref_convert.go" + + let set_convert_rendered = string_of_file "set_convert.go" + + let record_convert_rendered = string_of_file "record_convert.go" + + let interface_convert_rendered = string_of_file "interface_convert.go" + + let map_convert_rendered = string_of_file "map_convert.go" + + let enum_convert_rendered = string_of_file "enum_convert.go" + + let batch_convert_rendered = string_of_file "batch_convert.go" + + let option_convert_rendered = string_of_file "option_convert.go" + let tests = `QuickAndAutoDocumented [ @@ -336,6 +631,22 @@ module TemplatesTest = Generic.MakeStateless (struct ; (("Enum.mustache", enums), enums_rendered) ; (("APIErrors.mustache", api_errors), api_errors_rendered) ; (("APIMessages.mustache", api_messages), api_messages_rendered) + ; ( ("ConvertSimpleType.mustache", simple_type_convert) + , simple_type_rendered + ) + ; (("ConvertInt.mustache", int_convert), int_convert_rendered) + ; (("ConvertFloat.mustache", float_convert), float_convert_rendered) + ; (("ConvertTime.mustache", time_convert), time_convert_rendered) + ; (("ConvertRef.mustache", ref_string_convert), string_ref_rendered) + ; (("ConvertSet.mustache", set_convert), set_convert_rendered) + ; (("ConvertRecord.mustache", record_convert), record_convert_rendered) + ; ( ("ConvertInterface.mustache", Convert.interface) + , interface_convert_rendered + ) + ; (("ConvertMap.mustache", map_convert), map_convert_rendered) + ; (("ConvertEnum.mustache", enum_convert), enum_convert_rendered) + ; (("ConvertBatch.mustache", Convert.event_batch), batch_convert_rendered) + ; (("ConvertOption.mustache", option_convert), option_convert_rendered) ] end) @@ -363,12 +674,76 @@ module TestGeneratedJson = struct ] end +module TestConvertGeneratedJson = struct + open Convert + + let verify description verify_func actual = + Alcotest.(check bool) description true (verify_func actual) + + let param_types = TypesOfMessages.of_params objects + + let result_types = TypesOfMessages.of_results objects + + let verify_func = function + | Simple _ | Int _ | Float _ | Time _ | Ref _ -> + verify_simple_convert + | Option _ -> + verify_option_convert + | Set _ -> + verify_set_convert + | Record _ -> + verify_record_convert + | Enum _ -> + verify_enum_convert + | Map _ -> + verify_map_convert + + let convert_param_name = function + | Simple _ -> + "simple" + | Int _ -> + "int" + | Float _ -> + "float" + | Time _ -> + "time" + | Ref _ -> + "ref" + | Option _ -> + "option" + | Set _ -> + "set" + | Record _ -> + "record" + | Enum _ -> + "enum" + | Map _ -> + "map" + + let test types () = + List.iter + (fun ty -> + let param = Convert.of_ty ty in + let obj = Convert.to_json param in + let verify_func = verify_func param in + verify (convert_param_name param) verify_func obj + ) + types + + let tests = + [ + ("serialize", `Quick, test param_types) + ; ("deserialize", `Quick, test result_types) + ] +end + let tests = make_suite "gen_go_binding_" [ ("snake_to_camel", SnakeToCamelTest.tests) ; ("templates", TemplatesTest.tests) ; ("generated_mustache_jsons", TestGeneratedJson.tests) + ; ("generated_convert_jsons", TestConvertGeneratedJson.tests) ] let () = Alcotest.run "Gen go binding" tests From 6370b36dae572e471bb108b9dce69be83a790639 Mon Sep 17 00:00:00 2001 From: xueqingz Date: Mon, 22 Apr 2024 08:30:57 +0000 Subject: [PATCH 04/12] CP-47355, CP-47360: generate mustache template for xapi data module class messages Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/autogen/src/go.mod | 2 +- ocaml/sdk-gen/go/templates/Methods.mustache | 54 ++++++++++++++++ .../go/templates/SessionMethod.mustache | 64 +++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 ocaml/sdk-gen/go/templates/Methods.mustache create mode 100644 ocaml/sdk-gen/go/templates/SessionMethod.mustache diff --git a/ocaml/sdk-gen/go/autogen/src/go.mod b/ocaml/sdk-gen/go/autogen/src/go.mod index 0d33115cf62..9a8b7f9c133 100644 --- a/ocaml/sdk-gen/go/autogen/src/go.mod +++ b/ocaml/sdk-gen/go/autogen/src/go.mod @@ -1,3 +1,3 @@ module go/xenapi -go 1.22.0 +go 1.22.2 diff --git a/ocaml/sdk-gen/go/templates/Methods.mustache b/ocaml/sdk-gen/go/templates/Methods.mustache new file mode 100644 index 00000000000..32a0a5c8982 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/Methods.mustache @@ -0,0 +1,54 @@ +{{#messages}} +// {{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#has_error}} +// +// Errors: +{{/has_error}} +{{#errors}} +// {{name}} - {{doc}} +{{/errors}} +func ({{name_internal}}) {{method_name_exported}}({{#params}}{{#first}}session *Session{{/first}}{{^first}}, {{name_internal}} {{type}}{{/first}}{{/params}}) ({{#result}}retval {{type}}, {{/result}}err error) { + method := "{{class_name}}.{{method_name}}" +{{#params}} + {{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#first}}session.ref{{/first}}{{^first}}{{name_internal}}{{/first}}) + if err != nil { + return + } +{{/params}} + {{#result}}result, err := {{/result}}{{^result}}_, err = {{/result}}session.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}}) +{{#result}} + if err != nil { + return + } + retval, err = deserialize{{func_name_suffix}}(method+" -> ", result) +{{/result}} + return +} + +{{#async}} +// Async{{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#has_error}} +// +// Errors: +{{/has_error}} +{{#errors}} +// {{name}} - {{doc}} +{{/errors}} +func ({{name_internal}}) Async{{method_name_exported}}({{#params}}{{#first}}session *Session{{/first}}{{^first}}, {{name_internal}} {{type}}{{/first}}{{/params}}) (retval TaskRef, err error) { + method := "Async.{{class_name}}.{{method_name}}" +{{#params}} + {{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#first}}session.ref{{/first}}{{^first}}{{name_internal}}{{/first}}) + if err != nil { + return + } +{{/params}} + result, err := session.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}}) + if err != nil { + return + } + retval, err = deserializeTaskRef(method+" -> ", result) + return +} + +{{/async}} +{{/messages}} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/SessionMethod.mustache b/ocaml/sdk-gen/go/templates/SessionMethod.mustache new file mode 100644 index 00000000000..b73fb057379 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/SessionMethod.mustache @@ -0,0 +1,64 @@ +{{#messages}} +// {{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#has_error}} +// +// Errors: +{{/has_error}} +{{#errors}} +// {{name}} - {{doc}} +{{/errors}} +func (class *Session) {{method_name_exported}}({{#func_params}}{{^first}}, {{/first}}{{name_internal}} {{type}}{{/func_params}}) ({{#result}}retval {{type}}, {{/result}}err error) { + method := "{{class_name}}.{{method_name}}" +{{#params}} + {{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#is_session_id}}class.ref{{/is_session_id}}{{^is_session_id}}{{name_internal}}{{/is_session_id}}) + if err != nil { + return + } +{{/params}} + {{#result}}result, err := {{/result}}{{^result}}_, err = {{/result}}class.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}}) +{{#result}} + if err != nil { + return + } + retval, err = deserialize{{func_name_suffix}}(method+" -> ", result) +{{/result}} +{{#session_login}} + if err != nil { + return + } + class.ref = retval + err = setSessionDetails(class) +{{/session_login}} +{{#session_logout}} + class.ref = "" +{{/session_logout}} + return +} + +{{#async}} +// Async{{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#has_error}} +// +// Errors: +{{/has_error}} +{{#errors}} +// {{name}} - {{doc}} +{{/errors}} +func (class *Session) Async{{method_name_exported}}({{#func_params}}{{^first}}, {{/first}}{{name_internal}} {{type}}{{/func_params}}) (retval TaskRef, err error) { + method := "Async.{{class_name}}.{{method_name}}" +{{#params}} + {{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#is_session_id}}class.ref{{/is_session_id}}{{^is_session_id}}{{name_internal}}{{/is_session_id}}) + if err != nil { + return + } +{{/params}} + result, err := class.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}}) + if err != nil { + return + } + retval, err = deserializeTaskRef(method+" -> ", result) + return +} + +{{/async}} +{{/messages}} \ No newline at end of file From f2b241ebbb48dc3d820635dcb98c7de5b996ee1b Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Wed, 24 Apr 2024 09:56:36 +0800 Subject: [PATCH 05/12] CP-47354: Generate messages functions Golang code for all classes Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_binding.ml | 15 +- ocaml/sdk-gen/go/gen_go_helper.ml | 174 +++++++++- ocaml/sdk-gen/go/gen_go_helper.mli | 10 + ocaml/sdk-gen/go/test_data/methods.go | 37 +++ ocaml/sdk-gen/go/test_data/session_method.go | 38 +++ ocaml/sdk-gen/go/test_gen_go.ml | 321 ++++++++++++++++++- 6 files changed, 572 insertions(+), 23 deletions(-) create mode 100644 ocaml/sdk-gen/go/test_data/methods.go create mode 100644 ocaml/sdk-gen/go/test_data/session_method.go diff --git a/ocaml/sdk-gen/go/gen_go_binding.ml b/ocaml/sdk-gen/go/gen_go_binding.ml index 7e7a6ad726f..83f181328b5 100644 --- a/ocaml/sdk-gen/go/gen_go_binding.ml +++ b/ocaml/sdk-gen/go/gen_go_binding.ml @@ -104,7 +104,20 @@ let main destdir = render_template "FileHeader.mustache" obj ~newline:true () in let record_rendered = render_template "Record.mustache" obj () in - let rendered = header_rendered ^ record_rendered in + let methods_rendered = + if name = "session" then + render_template "SessionMethod.mustache" obj () + else + render_template "Methods.mustache" obj () + in + let rendered = + let first_half = header_rendered ^ record_rendered in + match methods_rendered with + | "" -> + first_half + | _ -> + first_half ^ "\n" ^ methods_rendered + in let output_file = name ^ ".go" in generate_file ~rendered ~destdir ~output_file ) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 92f2bf97dd9..731feef8586 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -39,7 +39,7 @@ let render_template template_file json ?(newline = false) () = let templ = string_of_file (templates_dir // template_file) |> Mustache.of_string in - let renndered = Mustache.render templ json in + let renndered = Mustache.render ~strict:true templ json in if newline then renndered ^ "\n" else renndered let generate_file ~rendered ~destdir ~output_file = @@ -60,7 +60,7 @@ module Json = struct let merge_maps m maps = List.fold_left (fun acc map -> StringMap.union choose_enum acc map) m maps - let rec func_name_suffix ty = + let rec suffix_of_type ty = match ty with | SecretString | String -> "String" @@ -75,17 +75,17 @@ module Json = struct | Enum (name, _) -> "Enum" ^ snake_to_camel name | Set ty -> - func_name_suffix ty ^ "Set" + suffix_of_type ty ^ "Set" | Map (ty1, ty2) -> - let k_suffix = func_name_suffix ty1 in - let v_suffix = func_name_suffix ty2 in + let k_suffix = suffix_of_type ty1 in + let v_suffix = suffix_of_type ty2 in k_suffix ^ "To" ^ v_suffix ^ "Map" | Ref r -> snake_to_camel r ^ "Ref" | Record r -> snake_to_camel r ^ "Record" | Option ty -> - func_name_suffix ty + suffix_of_type ty let rec string_of_ty_with_enums ty : string * enums = match ty with @@ -203,9 +203,149 @@ module Json = struct | _ -> [("event", `Null); ("session", `Null)] + let of_result obj msg = + match msg.msg_result with + | None -> + `Null + | Some (t, _d) -> + if obj.name = "event" && String.lowercase_ascii msg.msg_name = "from" + then + `O + [ + ("type", `String "EventBatch") + ; ("func_name_suffix", `String "EventBatch") + ] + else + let t', _ = string_of_ty_with_enums t in + `O + [ + ("type", `String t') + ; ("func_name_suffix", `String (suffix_of_type t)) + ] + + let of_params params = + let name_internal name = + let name = name |> snake_to_camel |> String.uncapitalize_ascii in + match name with "type" -> "typeKey" | "interface" -> "inter" | _ -> name + in + let of_param param = + let suffix_of_type = suffix_of_type param.param_type in + let t, _e = string_of_ty_with_enums param.param_type in + let name = param.param_name in + [ + ("is_session_id", `Bool (name = "session_id")) + ; ("type", `String t) + ; ("name", `String name) + ; ("name_internal", `String (name_internal name)) + ; ("doc", `String param.param_doc) + ; ("func_name_suffix", `String suffix_of_type) + ] + in + (* We use ',' to seprate params in Go function, we should ignore ',' before first param, + for example `func(a type1, b type2)` is wanted rather than `func(, a type1, b type2)`. + *) + let add_first = function + | head :: rest -> + let head = `O (("first", `Bool true) :: of_param head) in + let rest = + List.map + (fun item -> `O (("first", `Bool false) :: of_param item)) + rest + in + head :: rest + | [] -> + [] + in + `A (add_first params) + + let of_error e = `O [("name", `String e.err_name); ("doc", `String e.err_doc)] + + let of_errors = function + | [] -> + `Null + | errors -> + `A (List.map of_error errors) + + let add_session_info class_name method_name = + match (class_name, method_name) with + | "session", "login_with_password" + | "session", "slave_local_login_with_password" -> + [("session_login", `Bool true); ("session_logout", `Bool false)] + | "session", "logout" | "session", "local_logout" -> + [("session_login", `Bool false); ("session_logout", `Bool true)] + | _ -> + [("session_login", `Bool false); ("session_logout", `Bool false)] + + let desc_of_msg msg ctor_fields = + let ctor = + if msg.msg_tag = FromObject Make then + Printf.sprintf " The constructor args are: %s (* = non-optional)." + ctor_fields + else + "" + in + match msg.msg_doc ^ ctor with + | "" -> + `Null + | desc -> + `String (String.trim desc) + + let ctor_fields_of_obj obj = + Datamodel_utils.fields_of_obj obj + |> List.filter (function + | {qualifier= StaticRO | RW; _} -> + true + | _ -> + false + ) + |> List.map (fun f -> + String.concat "_" f.full_name + ^ if f.default_value = None then "*" else "" + ) + |> String.concat ", " + + let messages_of_obj obj = + let ctor_fields = ctor_fields_of_obj obj in + let params_in_msg msg = + if msg.msg_session then + session_id :: msg.msg_params + else + msg.msg_params + in + List.map + (fun msg -> + let params = params_in_msg msg |> of_params in + let base_assoc_list = + [ + ("method_name", `String msg.msg_name) + ; ("class_name", `String obj.name) + ; ("class_name_exported", `String (snake_to_camel obj.name)) + ; ("method_name_exported", `String (snake_to_camel msg.msg_name)) + ; ("description", desc_of_msg msg ctor_fields) + ; ("result", of_result obj msg) + ; ("params", params) + ; ("errors", of_errors msg.msg_errors) + ; ("has_error", `Bool (msg.msg_errors <> [])) + ; ("async", `Bool msg.msg_async) + ] + in + (* Since the param of `session *Session` isn't needed in functions of session object, + we add a special "func_params" field for session object to ignore `session *Session`.*) + if obj.name = "session" then + `O + (("func_params", msg.msg_params |> of_params) + :: (add_session_info obj.name msg.msg_name @ base_assoc_list) + ) + else + `O base_assoc_list + ) + obj.messages + let xenapi objs = List.map (fun obj -> + let obj_name = snake_to_camel obj.name in + let name_internal = String.uncapitalize_ascii obj_name in let fields = Datamodel_utils.fields_of_obj obj in let types = List.map (fun field -> field.ty) fields in let modules = @@ -213,12 +353,14 @@ module Json = struct in let base_assoc_list = [ - ("name", `String (snake_to_camel obj.name)) + ("name", `String obj_name) + ; ("name_internal", `String name_internal) ; ("description", `String (String.trim obj.description)) ; ( "fields" , `A (get_event_snapshot obj.name @ List.map of_field fields) ) ; ("modules", modules) + ; ("messages", `A (messages_of_obj obj)) ] in let assoc_list = base_assoc_list @ get_event_session_value obj.name in @@ -369,7 +511,7 @@ module Convert = struct |> Option.value ~default:[] |> List.rev_map (fun field -> ( String.concat "_" field.full_name - , Json.func_name_suffix field.ty + , Json.suffix_of_type field.ty , match field.ty with Option _ -> true | _ -> false ) ) @@ -395,13 +537,13 @@ module Convert = struct let items = List.map (fun (k, _) -> {value= k; name= name ^ snake_to_camel k}) kv in - Enum {func_suffix= Json.func_name_suffix ty; value_ty= name; items} + Enum {func_suffix= Json.suffix_of_type ty; value_ty= name; items} | Set ty as set -> - let fp_ty = Json.func_name_suffix ty in + let fp_ty = Json.suffix_of_type ty in let ty, _ = Json.string_of_ty_with_enums ty in Set { - func_suffix= Json.func_name_suffix set + func_suffix= Json.suffix_of_type set ; value_ty= ty ; item_fp_type= fp_ty } @@ -409,13 +551,13 @@ module Convert = struct let name, _ = Json.string_of_ty_with_enums ty in Map { - func_suffix= Json.func_name_suffix ty + func_suffix= Json.suffix_of_type ty ; value_ty= name - ; key_ty= Json.func_name_suffix ty1 - ; val_ty= Json.func_name_suffix ty2 + ; key_ty= Json.suffix_of_type ty1 + ; val_ty= Json.suffix_of_type ty2 } | Ref _ as ty -> - let name = Json.func_name_suffix ty in + let name = Json.suffix_of_type ty in Ref {func_suffix= name; value_ty= name} | Record r -> let name = snake_to_camel r ^ "Record" in @@ -435,7 +577,7 @@ module Convert = struct in Record {func_suffix= name; value_ty= name; fields} | Option ty -> - Option {func_suffix= Json.func_name_suffix ty} + Option {func_suffix= Json.suffix_of_type ty} let of_serialize params = `O [("serialize", `A [to_json params]); ("deserialize", `Null)] diff --git a/ocaml/sdk-gen/go/gen_go_helper.mli b/ocaml/sdk-gen/go/gen_go_helper.mli index 29a301b965a..ffa5ac673c9 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.mli +++ b/ocaml/sdk-gen/go/gen_go_helper.mli @@ -22,6 +22,16 @@ val generate_file : rendered:string -> destdir:string -> output_file:string -> unit module Json : sig + type enum = (string * string) list + + module StringMap : Map.S with type key = string + + type enums = enum StringMap.t + + val suffix_of_type : Datamodel_types.ty -> string + + val string_of_ty_with_enums : Datamodel_types.ty -> string * enums + val xenapi : Datamodel_types.obj list -> (string * Mustache.Json.t) list val all_enums : Datamodel_types.obj list -> Mustache.Json.t diff --git a/ocaml/sdk-gen/go/test_data/methods.go b/ocaml/sdk-gen/go/test_data/methods.go new file mode 100644 index 00000000000..3e80d71b0f7 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/methods.go @@ -0,0 +1,37 @@ +// GetLog: GetLog Get the host log file +func (host) GetLog(session *Session, host HostRef) (retval string, err error) { + method := "host.get_log" + sessionIDArg, err := serializeSessionRef(fmt.Sprintf("%s(%s)", method, "session_id"), session.ref) + if err != nil { + return + } + hostArg, err := serializeHostRef(fmt.Sprintf("%s(%s)", method, "host"), host) + if err != nil { + return + } + result, err := session.client.sendCall(method, sessionIDArg, hostArg) + if err != nil { + return + } + retval, err = deserializeString(method+" -> ", result) + return +} + +// AsyncGetLog: GetLog Get the host log file +func (host) AsyncGetLog(session *Session, host HostRef) (retval TaskRef, err error) { + method := "Async.host.get_log" + sessionIDArg, err := serializeSessionRef(fmt.Sprintf("%s(%s)", method, "session_id"), session.ref) + if err != nil { + return + } + hostArg, err := serializeHostRef(fmt.Sprintf("%s(%s)", method, "host"), host) + if err != nil { + return + } + result, err := session.client.sendCall(method, sessionIDArg, hostArg) + if err != nil { + return + } + retval, err = deserializeTaskRef(method+" -> ", result) + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/session_method.go b/ocaml/sdk-gen/go/test_data/session_method.go new file mode 100644 index 00000000000..b476f4606a8 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/session_method.go @@ -0,0 +1,38 @@ +// LoginWithPassword: Attempt to authenticate the user); returning a session reference if successful +// +// Errors: +// SESSION_AUTHENTICATION_FAILED - The credentials given by the user are incorrect +func (class *Session) LoginWithPassword(uname string, pwd string) (retval SessionRef, err error) { + method := "session.login_with_password" + unameArg, err := serializeString(fmt.Sprintf("%s(%s)", method, "uname"), uname) + if err != nil { + return + } + pwdArg, err := serializeString(fmt.Sprintf("%s(%s)", method, "pwd"), pwd) + if err != nil { + return + } + result, err := class.client.sendCall(method, unameArg, pwdArg) + if err != nil { + return + } + retval, err = deserializeSessionRef(method+" -> ", result) + if err != nil { + return + } + class.ref = retval + err = setSessionDetails(class) + return +} + +// Logout: Logout Log out of a session +func (class *Session) Logout() (err error) { + method := "session.logout" + sessionIDArg, err := serializeSessionRef(fmt.Sprintf("%s(%s)", method, "session_id"), class.ref) + if err != nil { + return + } + _, err = class.client.sendCall(method, sessionIDArg) + class.ref = "" + return +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 31964f7751e..590b4e3b77e 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -51,7 +51,6 @@ let schema_check keys checker members = let keys' = List.map (fun (k, _) -> k) members in compare_keys keys keys' && List.for_all checker members -(* field *) let verify_field_member = function | "name", `String _ | "description", `String _ | "type", `String _ -> true @@ -66,7 +65,134 @@ let verify_field = function | _ -> false -(* module *) +let result_keys = ["type"; "func_partial_type"] + +let verify_result_member = function + | "type", `String _ | "func_partial_type", `String _ -> + true + | _ -> + false + +let error_keys = ["name"; "doc"] + +let verify_error_member = function + | "name", `String _ | "doc", `String _ -> + true + | _ -> + false + +let verify_error = function + | `O error -> + schema_check error_keys verify_error_member error + | _ -> + false + +let param_keys = + [ + "is_session_id" + ; "type" + ; "name" + ; "name_internal" + ; "doc" + ; "func_partial_type" + ; "first" + ] + +let verify_param_member = function + | "is_session_id", `Bool _ + | "first", `Bool _ + | "type", `String _ + | "name", `String _ + | "name_internal", `String _ + | "doc", `String _ + | "func_partial_type", `String _ -> + true + | _ -> + false + +let verify_param = function + | `O param -> + schema_check param_keys verify_param_member param + | _ -> + false + +let verify_message_member = function + | "method_name", `String _ + | "class_name", `String _ + | "class_name_exported", `String _ + | "method_name_exported", `String _ -> + true + | "description", `String _ | "description", `Null -> + true + | "result", `Null -> + true + | "result", `O result -> + schema_check result_keys verify_result_member result + | "params", `A params -> + List.for_all verify_param params + | "errors", `A errors -> + List.for_all verify_error errors + | "async", `Bool _ | "has_error", `Bool _ | "errors", `Null -> + true + | _ -> + false + +let verify_sesseion_message_member = function + | "method_name", `String _ + | "class_name", `String _ + | "class_name_exported", `String _ + | "method_name_exported", `String _ -> + true + | "description", `String _ | "description", `Null -> + true + | "result", `Null -> + true + | "result", `O result -> + schema_check result_keys verify_result_member result + | "params", `A params -> + List.for_all verify_param params + | "header_params", `A params -> + List.for_all verify_param params + | "errors", `A errors -> + List.for_all verify_error errors + | "async", `Bool _ + | "has_error", `Bool _ + | "session_login", `Bool _ + | "session_logout", `Bool _ + | "errors", `Null -> + true + | _ -> + false + +let message_keys = + [ + "method_name" + ; "class_name" + ; "class_name_exported" + ; "method_name_exported" + ; "description" + ; "result" + ; "params" + ; "errors" + ; "has_error" + ; "async" + ] + +let session_message_keys = + ["session_login"; "session_logout"; "header_params"] @ message_keys + +let verify_message = function + | `O members -> + let class_name = + List.assoc_opt "class_name" members |> Option.value ~default:`Null + in + if class_name <> `String "session" then + schema_check message_keys verify_message_member members + else + schema_check session_message_keys verify_sesseion_message_member members + | _ -> + false + let verify_module_member = function | "name", `String _ -> true @@ -83,7 +209,6 @@ let verify_modules_item = function | _ -> false -(* modules *) let modules_keys = ["import"; "items"] let verify_modules_member = function @@ -96,7 +221,6 @@ let verify_modules_member = function let enum_values_keys = ["value"; "doc"; "name"; "type"] -(* enums *) let verify_enum_values_member = function | "value", `String _ | "doc", `String _ @@ -136,7 +260,7 @@ let verify_enums : Mustache.Json.t -> bool = function (* obj *) let verify_obj_member = function - | "name", `String _ | "description", `String _ -> + | "name", `String _ | "description", `String _ | "name_internal", `String _ -> true | "event", `Bool _ | "event", `Null -> true @@ -144,6 +268,8 @@ let verify_obj_member = function true | "fields", `A fields -> List.for_all verify_field fields + | "messages", `A messages -> + List.for_all verify_message messages | "modules", `Null -> true | "modules", `O members -> @@ -151,7 +277,17 @@ let verify_obj_member = function | _ -> false -let obj_keys = ["name"; "description"; "fields"; "modules"; "event"; "session"] +let obj_keys = + [ + "name" + ; "description" + ; "name_internal" + ; "fields" + ; "messages" + ; "modules" + ; "event" + ; "session" + ] let verify_obj = function | `O members -> @@ -574,6 +710,173 @@ let option_convert : Mustache.Json.t = let array = [`O [("func_name_suffix", `String "SrStatRecord")]] in `O [("serialize", `A array); ("deserialize", `A array)] +let session_messages : Mustache.Json.t = + `O + [ + ( "messages" + , `A + [ + `O + [ + ("session_login", `Bool true) + ; ("session_logout", `Bool false) + ; ("class_name", `String "session") + ; ("name_internal", `String "") + ; ("method_name", `String "login_with_password") + ; ("method_name_exported", `String "LoginWithPassword") + ; ( "description" + , `String + "Attempt to authenticate the user); returning a session \ + reference if successful" + ) + ; ("async", `Bool false) + ; ( "header_params" + , `A + [ + `O + [ + ("type", `String "string") + ; ("name", `String "uname") + ; ("name_internal", `String "uname") + ; ("func_partial_type", `String "String") + ; ("first", `Bool true) + ; ("is_session_id", `Bool false) + ] + ; `O + [ + ("type", `String "string") + ; ("name", `String "pwd") + ; ("name_internal", `String "pwd") + ; ("func_name_suffix", `String "String") + ; ("is_session_id", `Bool false) + ] + ] + ) + ; ( "params" + , `A + [ + `O + [ + ("type", `String "string") + ; ("name", `String "uname") + ; ("name_internal", `String "uname") + ; ("func_partial_type", `String "String") + ; ("first", `Bool true) + ; ("is_session_id", `Bool false) + ] + ; `O + [ + ("type", `String "string") + ; ("name", `String "pwd") + ; ("name_internal", `String "pwd") + ; ("func_name_suffix", `String "String") + ; ("is_session_id", `Bool false) + ] + ] + ) + ; ( "result" + , `O + [ + ("type", `String "SessionRef") + ; ("func_partial_type", `String "SessionRef") + ] + ) + ; ("has_error", `Bool true) + ; ( "errors" + , `A + [ + `O + [ + ("name", `String "SESSION_AUTHENTICATION_FAILED") + ; ( "doc" + , `String + "The credentials given by the user are incorrect" + ) + ] + ] + ) + ] + ; `O + [ + ("session_logout", `Bool true) + ; ("session_login", `Bool false) + ; ("class_name", `String "session") + ; ("class_name_exported", `String "Session") + ; ("method_name", `String "logout") + ; ("method_name_exported", `String "Logout") + ; ("description", `String "Logout Log out of a session") + ; ("async", `Bool false) + ; ("func_params", `A []) + ; ( "params" + , `A + [ + `O + [ + ("type", `String "SessionRef") + ; ("name", `String "session_id") + ; ("name_internal", `String "sessionID") + ; ("func_name_suffix", `String "SessionRef") + ; ("is_session_id", `Bool true) + ] + ] + ) + ; ("result", `Null) + ; ("has_error", `Bool false) + ; ("errors", `A []) + ] + ] + ) + ] + +let messages : Mustache.Json.t = + `O + [ + ( "messages" + , `A + [ + `O + [ + ("class_name", `String "host") + ; ("name_internal", `String "host") + ; ("method_name", `String "get_log") + ; ("method_name_exported", `String "GetLog") + ; ("description", `String "GetLog Get the host log file") + ; ("async", `Bool true) + ; ( "params" + , `A + [ + `O + [ + ("type", `String "SessionRef") + ; ("name", `String "session_id") + ; ("name_internal", `String "sessionID") + ; ("func_name_suffix", `String "SessionRef") + ; ("first", `Bool true) + ] + ; `O + [ + ("type", `String "HostRef") + ; ("name", `String "host") + ; ("name_internal", `String "host") + ; ("func_name_suffix", `String "HostRef") + ; ("first", `Bool false) + ] + ] + ) + ; ( "result" + , `O + [ + ("type", `String "string") + ; ("func_partial_type", `String "String") + ] + ) + ; ("has_error", `Bool false) + ; ("errors", `A []) + ] + ] + ) + ] + module TemplatesTest = Generic.MakeStateless (struct module Io = struct type input_t = string * Mustache.Json.t @@ -595,6 +898,10 @@ module TemplatesTest = Generic.MakeStateless (struct let enums_rendered = string_of_file "enum.go" + let methods_rendered = string_of_file "methods.go" + + let session_method_rendered = string_of_file "session_method.go" + let api_errors_rendered = string_of_file "api_errors.go" let api_messages_rendered = string_of_file "api_messages.go" @@ -629,6 +936,8 @@ module TemplatesTest = Generic.MakeStateless (struct (("FileHeader.mustache", header), file_header_rendered) ; (("Record.mustache", record), record_rendered) ; (("Enum.mustache", enums), enums_rendered) + ; (("Methods.mustache", messages), methods_rendered) + ; (("SessionMethod.mustache", session_messages), session_method_rendered) ; (("APIErrors.mustache", api_errors), api_errors_rendered) ; (("APIMessages.mustache", api_messages), api_messages_rendered) ; ( ("ConvertSimpleType.mustache", simple_type_convert) From 51f2e94831f61110635be7d70c55c1379a747b49 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Mon, 6 May 2024 20:15:36 +0800 Subject: [PATCH 06/12] CP-47354: add unit tests for `func_name_suffix` and `string_of_ty_with_enums` Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/test_gen_go.ml | 145 +++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 11 deletions(-) diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 590b4e3b77e..7609452ecfa 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -65,10 +65,10 @@ let verify_field = function | _ -> false -let result_keys = ["type"; "func_partial_type"] +let result_keys = ["type"; "func_name_suffix"] let verify_result_member = function - | "type", `String _ | "func_partial_type", `String _ -> + | "type", `String _ | "func_name_suffix", `String _ -> true | _ -> false @@ -94,7 +94,7 @@ let param_keys = ; "name" ; "name_internal" ; "doc" - ; "func_partial_type" + ; "func_name_suffix" ; "first" ] @@ -105,7 +105,7 @@ let verify_param_member = function | "name", `String _ | "name_internal", `String _ | "doc", `String _ - | "func_partial_type", `String _ -> + | "func_name_suffix", `String _ -> true | _ -> false @@ -151,7 +151,7 @@ let verify_sesseion_message_member = function schema_check result_keys verify_result_member result | "params", `A params -> List.for_all verify_param params - | "header_params", `A params -> + | "func_params", `A params -> List.for_all verify_param params | "errors", `A errors -> List.for_all verify_error errors @@ -179,7 +179,7 @@ let message_keys = ] let session_message_keys = - ["session_login"; "session_logout"; "header_params"] @ message_keys + ["session_login"; "session_logout"; "func_params"] @ message_keys let verify_message = function | `O members -> @@ -730,7 +730,7 @@ let session_messages : Mustache.Json.t = reference if successful" ) ; ("async", `Bool false) - ; ( "header_params" + ; ( "func_params" , `A [ `O @@ -738,7 +738,7 @@ let session_messages : Mustache.Json.t = ("type", `String "string") ; ("name", `String "uname") ; ("name_internal", `String "uname") - ; ("func_partial_type", `String "String") + ; ("func_name_suffix", `String "String") ; ("first", `Bool true) ; ("is_session_id", `Bool false) ] @@ -760,7 +760,7 @@ let session_messages : Mustache.Json.t = ("type", `String "string") ; ("name", `String "uname") ; ("name_internal", `String "uname") - ; ("func_partial_type", `String "String") + ; ("func_name_suffix", `String "String") ; ("first", `Bool true) ; ("is_session_id", `Bool false) ] @@ -778,7 +778,7 @@ let session_messages : Mustache.Json.t = , `O [ ("type", `String "SessionRef") - ; ("func_partial_type", `String "SessionRef") + ; ("func_name_suffix", `String "SessionRef") ] ) ; ("has_error", `Bool true) @@ -851,6 +851,8 @@ let messages : Mustache.Json.t = ; ("name", `String "session_id") ; ("name_internal", `String "sessionID") ; ("func_name_suffix", `String "SessionRef") + ; ("session", `Bool true) + ; ("session_class", `Bool false) ; ("first", `Bool true) ] ; `O @@ -867,7 +869,7 @@ let messages : Mustache.Json.t = , `O [ ("type", `String "string") - ; ("func_partial_type", `String "String") + ; ("func_name_suffix", `String "String") ] ) ; ("has_error", `Bool false) @@ -983,6 +985,38 @@ module TestGeneratedJson = struct ] end +module SuffixOfTypeTest = Generic.MakeStateless (struct + open Datamodel_types + + module Io = struct + type input_t = ty + + type output_t = string + + let string_of_input_t = Json.suffix_of_type + + let string_of_output_t = Test_printers.string + end + + let transform = Json.suffix_of_type + + let tests = + `QuickAndAutoDocumented + [ + (SecretString, "String") + ; (String, "String") + ; (Int, "Int") + ; (Float, "Float") + ; (Bool, "Bool") + ; (Enum ("update_sync", [("a", "b"); ("c", "d")]), "EnumUpdateSync") + ; (Set String, "StringSet") + ; (Map (Int, String), "IntToStringMap") + ; (Ref "pool", "PoolRef") + ; (Record "pool", "PoolRecord") + ; (Option String, "String") + ] +end) + module TestConvertGeneratedJson = struct open Convert @@ -1046,10 +1080,99 @@ module TestConvertGeneratedJson = struct ] end +module StringOfTyWithEnumsTest = struct + open Datamodel_types + module StringMap = Json.StringMap + + let verify description verify_func actual = + Alcotest.(check bool) description true (verify_func actual) + + let verify_string (ty, enums) = ty = "string" && enums = StringMap.empty + + let test_string () = + let ty, enums = Json.string_of_ty_with_enums String in + verify "String" verify_string (ty, enums) + + let test_secret_string () = + let ty, enums = Json.string_of_ty_with_enums SecretString in + verify "SecretString" verify_string (ty, enums) + + let verify_float (ty, enums) = ty = "float64" && enums = StringMap.empty + + let test_float () = + let ty, enums = Json.string_of_ty_with_enums Float in + verify "Float" verify_float (ty, enums) + + let verify_bool (ty, enums) = ty = "bool" && enums = StringMap.empty + + let test_bool () = + let ty, enums = Json.string_of_ty_with_enums Bool in + verify "bool" verify_bool (ty, enums) + + let verify_datetime (ty, enums) = ty = "time.Time" && enums = StringMap.empty + + let test_datetime () = + let ty, enums = Json.string_of_ty_with_enums DateTime in + verify "datetime" verify_datetime (ty, enums) + + let enum_lst = [("a", "b"); ("c", "d")] + + let verify_enum (ty, enums) = + ty = "UpdateSync" && enums = StringMap.singleton "UpdateSync" enum_lst + + let test_enum () = + let ty, enums = + Json.string_of_ty_with_enums (Enum ("update_sync", enum_lst)) + in + verify "enum" verify_enum (ty, enums) + + let verify_ref (ty, enums) = ty = "PoolRef" && enums = StringMap.empty + + let test_ref () = + let ty, enums = Json.string_of_ty_with_enums (Ref "pool") in + verify "ref" verify_ref (ty, enums) + + let verify_record (ty, enums) = ty = "PoolRecord" && enums = StringMap.empty + + let test_record () = + let ty, enums = Json.string_of_ty_with_enums (Record "pool") in + verify "datetime" verify_record (ty, enums) + + let test_option () = + let ty, enums = Json.string_of_ty_with_enums (Option String) in + verify "datetime" verify_string (ty, enums) + + let verify_map (ty, enums) = + ty = "map[int]UpdateSync" + && enums = StringMap.singleton "UpdateSync" enum_lst + + let test_map () = + let ty, enums = + Json.string_of_ty_with_enums (Map (Int, Enum ("update_sync", enum_lst))) + in + verify "map" verify_map (ty, enums) + + let tests = + [ + ("String", `Quick, test_string) + ; ("SecretString", `Quick, test_secret_string) + ; ("Float", `Quick, test_float) + ; ("Bool", `Quick, test_bool) + ; ("DateTime", `Quick, test_datetime) + ; ("Enum", `Quick, test_enum) + ; ("Ref", `Quick, test_ref) + ; ("Record", `Quick, test_record) + ; ("Option", `Quick, test_option) + ; ("Map", `Quick, test_map) + ] +end + let tests = make_suite "gen_go_binding_" [ ("snake_to_camel", SnakeToCamelTest.tests) + ; ("suffix_of_type", SuffixOfTypeTest.tests) + ; ("string_of_ty_with_enums", StringOfTyWithEnumsTest.tests) ; ("templates", TemplatesTest.tests) ; ("generated_mustache_jsons", TestGeneratedJson.tests) ; ("generated_convert_jsons", TestConvertGeneratedJson.tests) From 0676c3c89c793391a40ee032eaebf620f2a71b6b Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 21:35:12 +0800 Subject: [PATCH 07/12] CP-48855: update templates (APIErrors, APIMessages, Record) Signed-off-by: xueqingz Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/templates/APIErrors.mustache | 3 ++- .../sdk-gen/go/templates/APIMessages.mustache | 2 +- ocaml/sdk-gen/go/templates/Record.mustache | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ocaml/sdk-gen/go/templates/APIErrors.mustache b/ocaml/sdk-gen/go/templates/APIErrors.mustache index 8128b5ef185..9bce9a085dc 100644 --- a/ocaml/sdk-gen/go/templates/APIErrors.mustache +++ b/ocaml/sdk-gen/go/templates/APIErrors.mustache @@ -1,6 +1,7 @@ +//nolint:gosec const ( {{#api_errors}} // - ERR_{{name}} = "{{name}}" + Error{{name}} = "{{value}}" {{/api_errors}} ) \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/APIMessages.mustache b/ocaml/sdk-gen/go/templates/APIMessages.mustache index 172e4e416c4..3bac101ff6f 100644 --- a/ocaml/sdk-gen/go/templates/APIMessages.mustache +++ b/ocaml/sdk-gen/go/templates/APIMessages.mustache @@ -1,6 +1,6 @@ const ( {{#api_messages}} // - MSG_{{name}} = "{{name}}" + Message{{name}} = "{{value}}" {{/api_messages}} ) \ No newline at end of file diff --git a/ocaml/sdk-gen/go/templates/Record.mustache b/ocaml/sdk-gen/go/templates/Record.mustache index e4d874ba978..b30e234e1cb 100644 --- a/ocaml/sdk-gen/go/templates/Record.mustache +++ b/ocaml/sdk-gen/go/templates/Record.mustache @@ -21,21 +21,23 @@ type EventBatch struct { // {{.}} {{/description}} {{#session}} -type {{name}}Class struct { - client *rpcClient - ref SessionRef +type {{name}} struct { + APIVersion APIVersion + client *rpcClient + ref SessionRef + XAPIVersion string } -func NewSession(opts *ClientOpts) *SessionClass { - client := NewJsonRPCClient(opts) - var session SessionClass +func NewSession(opts *ClientOpts) *Session { + client := newJSONRPCClient(opts) + var session Session session.client = client return &session } {{/session}} {{^session}} -type {{name}}Class struct{} +type {{name_internal}} struct{} -var {{name}} *{{name}}Class +var {{name}} {{name_internal}} {{/session}} \ No newline at end of file From 327260c4d3466586bbd1bb24ec628cc2d1a9afc1 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 21:44:57 +0800 Subject: [PATCH 08/12] CP-48855: adjust generated json for templates changed changed templates: APIVersions.mustache,APIErrors.mustache, Record.mustache Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_helper.ml | 29 +++++++++++++++-- ocaml/sdk-gen/go/test_data/api_errors.go | 7 +++-- ocaml/sdk-gen/go/test_data/api_messages.go | 4 +-- ocaml/sdk-gen/go/test_data/record.go | 14 +++++---- ocaml/sdk-gen/go/test_gen_go.ml | 36 ++++++++++++++++++---- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 731feef8586..5bca85289ca 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -368,11 +368,34 @@ module Json = struct ) objs + let of_api_message_or_error info = + let snake_to_camel (s : string) : string = + String.split_on_char '_' s + |> List.map (fun seg -> + let lower = String.lowercase_ascii seg in + match lower with + | "vm" + | "cpu" + | "tls" + | "xml" + | "url" + | "id" + | "uuid" + | "ip" + | "api" + | "eof" -> + String.uppercase_ascii lower + | _ -> + String.capitalize_ascii lower + ) + |> String.concat "" + in + `O [("name", `String (snake_to_camel info)); ("value", `String info)] + let api_messages = - List.map (fun (msg, _) -> `O [("name", `String msg)]) !Api_messages.msgList + List.map (fun (msg, _) -> of_api_message_or_error msg) !Api_messages.msgList - let api_errors = - List.map (fun error -> `O [("name", `String error)]) !Api_errors.errors + let api_errors = List.map of_api_message_or_error !Api_errors.errors end module Convert = struct diff --git a/ocaml/sdk-gen/go/test_data/api_errors.go b/ocaml/sdk-gen/go/test_data/api_errors.go index b14349c1885..1e6d67aba8a 100644 --- a/ocaml/sdk-gen/go/test_data/api_errors.go +++ b/ocaml/sdk-gen/go/test_data/api_errors.go @@ -1,6 +1,7 @@ +//nolint:gosec const ( // - ERR_MESSAGE_DEPRECATED = "MESSAGE_DEPRECATED" + ErrorMessageDeprecated = "MESSAGE_DEPRECATED" // - ERR_MESSAGE_REMOVED = "MESSAGE_REMOVED" -) + ErrorMessageRemoved = "MESSAGE_REMOVED" +) \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/api_messages.go b/ocaml/sdk-gen/go/test_data/api_messages.go index f91592ec010..7f54389eb67 100644 --- a/ocaml/sdk-gen/go/test_data/api_messages.go +++ b/ocaml/sdk-gen/go/test_data/api_messages.go @@ -1,6 +1,6 @@ const ( // - MSG_HA_STATEFILE_LOST = "HA_STATEFILE_LOST" + MessageHaStatefileLost = "HA_STATEFILE_LOST" // - MSG_METADATA_LUN_HEALTHY = "METADATA_LUN_HEALTHY" + MessageMetadataLunHealthy = "METADATA_LUN_HEALTHY" ) \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_data/record.go b/ocaml/sdk-gen/go/test_data/record.go index fd5908e19c0..c07e18b9fb9 100644 --- a/ocaml/sdk-gen/go/test_data/record.go +++ b/ocaml/sdk-gen/go/test_data/record.go @@ -8,14 +8,16 @@ type SessionRecord struct { type SessionRef string // A session -type SessionClass struct { - client *rpcClient - ref SessionRef +type Session struct { + APIVersion APIVersion + client *rpcClient + ref SessionRef + XAPIVersion string } -func NewSession(opts *ClientOpts) *SessionClass { - client := NewJsonRPCClient(opts) - var session SessionClass +func NewSession(opts *ClientOpts) *Session { + client := newJSONRPCClient(opts) + var session Session session.client = client return &session diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 7609452ecfa..b75f678e5a4 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -295,10 +295,18 @@ let verify_obj = function | _ -> false +let verify_msg_or_error_member = function + | "name", `String _ | "value", `String _ -> + true + | _ -> + false + +let keys_in_error_or_msg = ["name"; "value"] + let verify_msgs_or_errors lst = let verify_msg_or_error = function - | `O [("name", `String _)] -> - true + | `O members -> + schema_check keys_in_error_or_msg verify_msg_or_error_member members | _ -> false in @@ -547,8 +555,16 @@ let api_errors : Mustache.Json.t = ( "api_errors" , `A [ - `O [("name", `String "MESSAGE_DEPRECATED")] - ; `O [("name", `String "MESSAGE_REMOVED")] + `O + [ + ("name", `String "MessageDeprecated") + ; ("value", `String "MESSAGE_DEPRECATED") + ] + ; `O + [ + ("name", `String "MessageRemoved") + ; ("value", `String "MESSAGE_REMOVED") + ] ] ) ] @@ -559,8 +575,16 @@ let api_messages : Mustache.Json.t = ( "api_messages" , `A [ - `O [("name", `String "HA_STATEFILE_LOST")] - ; `O [("name", `String "METADATA_LUN_HEALTHY")] + `O + [ + ("name", `String "HaStatefileLost") + ; ("value", `String "HA_STATEFILE_LOST") + ] + ; `O + [ + ("name", `String "MetadataLunHealthy") + ; ("value", `String "METADATA_LUN_HEALTHY") + ] ] ) ] From 3527346ffc61602cf1abe67d0debf4cef04d10e2 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 21:37:31 +0800 Subject: [PATCH 09/12] CP-48855: add templates for option and APIVersions Signed-off-by: xueqingz Signed-off-by: Luca Zhang --- .../sdk-gen/go/templates/APIVersions.mustache | 103 ++++++++++++++++++ ocaml/sdk-gen/go/templates/Option.mustache | 4 + 2 files changed, 107 insertions(+) create mode 100644 ocaml/sdk-gen/go/templates/APIVersions.mustache create mode 100644 ocaml/sdk-gen/go/templates/Option.mustache diff --git a/ocaml/sdk-gen/go/templates/APIVersions.mustache b/ocaml/sdk-gen/go/templates/APIVersions.mustache new file mode 100644 index 00000000000..6c25e5a7035 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/APIVersions.mustache @@ -0,0 +1,103 @@ +type APIVersion int + +const ( +{{#releases}} + // {{branding}} ({{code_name}}) + APIVersion{{version_major}}_{{version_minor}}{{#first}} APIVersion = iota + 1{{/first}} +{{/releases}} + APIVersionLatest APIVersion = {{latest_version_index}} + APIVersionUnknown APIVersion = 99 +) + +func (v APIVersion) String() string { + switch v { +{{#releases}} + case APIVersion{{version_major}}_{{version_minor}}: + return "{{version_major}}.{{version_minor}}" +{{/releases}} + case APIVersionUnknown: + return "Unknown" + default: + return "Unknown" + } +} + +var APIVersionMap = map[string]APIVersion{ +{{#releases}} + // + "APIVersion{{version_major}}_{{version_minor}}": APIVersion{{version_major}}_{{version_minor}}, +{{/releases}} + // + "APIVersionLatest": APIVersionLatest, + // + "APIVersionUnknown": APIVersionUnknown, +} + +func GetAPIVersion(major int, minor int) APIVersion { + versionName := fmt.Sprintf("APIVersion%d_%d", major, minor) + apiVersion, ok := APIVersionMap[versionName] + if !ok { + apiVersion = APIVersionUnknown + } + + return apiVersion +} + +func getPoolMaster(session *Session) (HostRef, error) { + var master HostRef + poolRefs, err := Pool.GetAll(session) + if err != nil { + return master, err + } + if len(poolRefs) > 0 { + poolRecord, err := Pool.GetRecord(session, poolRefs[0]) + if err != nil { + return master, err + } + return poolRecord.Master, nil + } + return master, errors.New("pool master not found") +} + +func setSessionDetails(session *Session) error { + err := setAPIVersion(session) + if err != nil { + return err + } + err = setXAPIVersion(session) + if err != nil { + return err + } + return nil +} + +func setAPIVersion(session *Session) error { + session.APIVersion = APIVersionUnknown + masterRef, err := getPoolMaster(session) + if err != nil { + return err + } + hostRecord, err := Host.GetRecord(session, masterRef) + if err != nil { + return err + } + session.APIVersion = GetAPIVersion(hostRecord.APIVersionMajor, hostRecord.APIVersionMinor) + return nil +} + +func setXAPIVersion(session *Session) error { + masterRef, err := getPoolMaster(session) + if err != nil { + return err + } + hostRecord, err := Host.GetRecord(session, masterRef) + if err != nil { + return err + } + version, ok := hostRecord.SoftwareVersion["xapi"] + if !ok { + return errors.New("xapi version not found") + } + session.XAPIVersion = version + return nil +} diff --git a/ocaml/sdk-gen/go/templates/Option.mustache b/ocaml/sdk-gen/go/templates/Option.mustache new file mode 100644 index 00000000000..5066597f853 --- /dev/null +++ b/ocaml/sdk-gen/go/templates/Option.mustache @@ -0,0 +1,4 @@ +{{#option}} +type Option{{type_name_suffix}} *{{type}} + +{{/option}} \ No newline at end of file From 420c07656b9d87e7fe9a0bb03ce4bba52ddf0ed7 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 22:15:34 +0800 Subject: [PATCH 10/12] CP-48855: render options Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_binding.ml | 3 ++- ocaml/sdk-gen/go/gen_go_helper.ml | 27 ++++++++++++++++---- ocaml/sdk-gen/go/test_gen_go.ml | 41 ++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/ocaml/sdk-gen/go/gen_go_binding.ml b/ocaml/sdk-gen/go/gen_go_binding.ml index 83f181328b5..a97a91090cc 100644 --- a/ocaml/sdk-gen/go/gen_go_binding.ml +++ b/ocaml/sdk-gen/go/gen_go_binding.ml @@ -103,6 +103,7 @@ let main destdir = let header_rendered = render_template "FileHeader.mustache" obj ~newline:true () in + let options_rendered = render_template "Option.mustache" obj () in let record_rendered = render_template "Record.mustache" obj () in let methods_rendered = if name = "session" then @@ -111,7 +112,7 @@ let main destdir = render_template "Methods.mustache" obj () in let rendered = - let first_half = header_rendered ^ record_rendered in + let first_half = header_rendered ^ options_rendered ^ record_rendered in match methods_rendered with | "" -> first_half diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 5bca85289ca..5bf74d0864d 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -49,6 +49,8 @@ let generate_file ~rendered ~destdir ~output_file = ~finally:(fun () -> close_out out_chan) module Json = struct + open Xapi_stdext_std + type enum = (string * string) list module StringMap = Map.Make (String) @@ -115,7 +117,9 @@ module Json = struct | Record r -> (snake_to_camel r ^ "Record", StringMap.empty) | Option ty -> - string_of_ty_with_enums ty + let _, e = string_of_ty_with_enums ty in + let name = suffix_of_type ty in + ("Option" ^ name, e) let of_enum name vs = let name = snake_to_camel name in @@ -157,9 +161,7 @@ module Json = struct let modules_of_types types = let common = [`O [("name", `String "fmt"); ("sname", `Null)]] in - let items = - List.map modules_of_type types |> List.concat |> List.append common - in + let items = List.concat_map modules_of_type types |> List.append common in `O [("import", `Bool true); ("items", `A items)] let all_enums objs = @@ -341,13 +343,27 @@ module Json = struct ) obj.messages + let of_option ty = + let name, _ = string_of_ty_with_enums ty in + `O + [ + ("type", `String name); ("type_name_suffix", `String (suffix_of_type ty)) + ] + + let of_options types = + types + |> List.filter_map (function Option ty -> Some ty | _ -> None) + |> List.map of_option + let xenapi objs = List.map (fun obj -> let obj_name = snake_to_camel obj.name in let name_internal = String.uncapitalize_ascii obj_name in let fields = Datamodel_utils.fields_of_obj obj in - let types = List.map (fun field -> field.ty) fields in + let types = + List.map (fun field -> field.ty) fields |> Listext.List.setify + in let modules = match obj.messages with [] -> `Null | _ -> modules_of_types types in @@ -361,6 +377,7 @@ module Json = struct ) ; ("modules", modules) ; ("messages", `A (messages_of_obj obj)) + ; ("option", `A (of_options types)) ] in let assoc_list = base_assoc_list @ get_event_session_value obj.name in diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index b75f678e5a4..d8866f901c7 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -258,6 +258,20 @@ let verify_enums : Mustache.Json.t -> bool = function | _ -> false +let option_keys = ["type"; "type_name_suffix"] + +let verify_option_member = function + | "type", `String _ | "type_name_suffix", `String _ -> + true + | _ -> + false + +let verify_option = function + | `O members -> + schema_check option_keys verify_option_member members + | _ -> + false + (* obj *) let verify_obj_member = function | "name", `String _ | "description", `String _ | "name_internal", `String _ -> @@ -270,6 +284,8 @@ let verify_obj_member = function List.for_all verify_field fields | "messages", `A messages -> List.for_all verify_message messages + | "option", `A options -> + List.for_all verify_option options | "modules", `Null -> true | "modules", `O members -> @@ -287,6 +303,7 @@ let obj_keys = ; "modules" ; "event" ; "session" + ; "option" ] let verify_obj = function @@ -903,6 +920,21 @@ let messages : Mustache.Json.t = ) ] +let option = + `O + [ + ( "option" + , `A + [ + `O + [ + ("type", `String "string") + ; ("type_name_suffix", `String "String") + ] + ] + ) + ] + module TemplatesTest = Generic.MakeStateless (struct module Io = struct type input_t = string * Mustache.Json.t @@ -956,6 +988,8 @@ module TemplatesTest = Generic.MakeStateless (struct let option_convert_rendered = string_of_file "option_convert.go" + let option_rendered = "type OptionString *string" + let tests = `QuickAndAutoDocumented [ @@ -982,6 +1016,7 @@ module TemplatesTest = Generic.MakeStateless (struct ; (("ConvertEnum.mustache", enum_convert), enum_convert_rendered) ; (("ConvertBatch.mustache", Convert.event_batch), batch_convert_rendered) ; (("ConvertOption.mustache", option_convert), option_convert_rendered) + ; (("Option.mustache", option), option_rendered) ] end) @@ -1160,11 +1195,13 @@ module StringOfTyWithEnumsTest = struct let test_record () = let ty, enums = Json.string_of_ty_with_enums (Record "pool") in - verify "datetime" verify_record (ty, enums) + verify "record" verify_record (ty, enums) + + let verify_option (ty, enums) = ty = "OptionString" && enums = StringMap.empty let test_option () = let ty, enums = Json.string_of_ty_with_enums (Option String) in - verify "datetime" verify_string (ty, enums) + verify "option" verify_string (ty, enums) let verify_map (ty, enums) = ty = "map[int]UpdateSync" From 181d49dbbae3c6eb2d781316d8c8d3d6f4eef262 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 22:26:09 +0800 Subject: [PATCH 11/12] CP-48855: render APIVersion Signed-off-by: Luca Zhang --- ocaml/sdk-gen/common/CommonFunctions.ml | 32 ++++--- ocaml/sdk-gen/go/gen_go_binding.ml | 44 +++++++-- ocaml/sdk-gen/go/test_data/api_versions.go | 103 +++++++++++++++++++++ ocaml/sdk-gen/go/test_gen_go.ml | 82 +++++++++++++++- 4 files changed, 239 insertions(+), 22 deletions(-) create mode 100644 ocaml/sdk-gen/go/test_data/api_versions.go diff --git a/ocaml/sdk-gen/common/CommonFunctions.ml b/ocaml/sdk-gen/common/CommonFunctions.ml index 989448f49e2..cffa59b4276 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.ml +++ b/ocaml/sdk-gen/common/CommonFunctions.ml @@ -308,24 +308,32 @@ let json_releases = in try index_rec 0 list with Not_found -> -1 in - let json_of_rel x = + let of_rel x = let y = version_index_of x unique_version_bumps + 1 in - `O - [ - ( "code_name" - , `String (match x.code_name with Some r -> r | None -> "") - ) - ; ("version_major", `Float (float_of_int x.version_major)) - ; ("version_minor", `Float (float_of_int x.version_minor)) - ; ("branding", `String x.branding) - ; ("version_index", `Float (float_of_int y)) - ] + [ + ("code_name", `String (Option.value x.code_name ~default:"")) + ; ("version_major", `Float (float_of_int x.version_major)) + ; ("version_minor", `Float (float_of_int x.version_minor)) + ; ("branding", `String x.branding) + ; ("version_index", `Float (float_of_int y)) + ] + in + let of_rels releases = + match releases with + | [] -> + `A [] + | head :: tail -> + let head' = `O (("first", `Bool true) :: of_rel head) in + let tail' = + List.map (fun rel -> `O (("first", `Bool false) :: of_rel rel)) tail + in + `A (head' :: tail') in `O [ ("API_VERSION_MAJOR", `Float (Int64.to_float Datamodel.api_version_major)) ; ("API_VERSION_MINOR", `Float (Int64.to_float Datamodel.api_version_minor)) - ; ("releases", `A (List.map (fun x -> json_of_rel x) unique_version_bumps)) + ; ("releases", of_rels unique_version_bumps) ; ( "latest_version_index" , `Float (float_of_int (List.length unique_version_bumps)) ) diff --git a/ocaml/sdk-gen/go/gen_go_binding.ml b/ocaml/sdk-gen/go/gen_go_binding.ml index a97a91090cc..0203e16966c 100644 --- a/ocaml/sdk-gen/go/gen_go_binding.ml +++ b/ocaml/sdk-gen/go/gen_go_binding.ml @@ -24,6 +24,30 @@ let render_enums enums destdir = let rendered = header ^ enums ^ "\n" in generate_file ~rendered ~destdir ~output_file:"enums.go" +let render_api_versions destdir = + let header_json = + let name s = `O [("name", `String s); ("sname", `Null)] in + `O + [ + ( "modules" + , `O + [ + ("import", `Bool true) + ; ("items", `A (List.map name ["errors"; "fmt"])) + ] + ) + ] + in + let rendered = + let header = + render_template "FileHeader.mustache" header_json ~newline:true () + in + header + ^ render_template "APIVersions.mustache" CommonFunctions.json_releases + ~newline:true () + in + generate_file ~rendered ~destdir ~output_file:"api_versions.go" + let render_api_messages_and_errors destdir = let obj = `O @@ -93,6 +117,7 @@ let render_converts destdir = generate_file ~rendered ~destdir ~output_file:"convert.go" let main destdir = + render_api_versions destdir ; render_api_messages_and_errors destdir ; let enums = Json.all_enums objects in render_enums enums destdir ; @@ -104,7 +129,9 @@ let main destdir = render_template "FileHeader.mustache" obj ~newline:true () in let options_rendered = render_template "Option.mustache" obj () in - let record_rendered = render_template "Record.mustache" obj () in + let record_rendered = + render_template "Record.mustache" obj () ~newline:true + in let methods_rendered = if name = "session" then render_template "SessionMethod.mustache" obj () @@ -112,15 +139,14 @@ let main destdir = render_template "Methods.mustache" obj () in let rendered = - let first_half = header_rendered ^ options_rendered ^ record_rendered in - match methods_rendered with - | "" -> - first_half - | _ -> - first_half ^ "\n" ^ methods_rendered + let rendered = + [header_rendered; options_rendered; record_rendered; methods_rendered] + |> String.concat "" + |> String.trim + in + rendered ^ "\n" in - let output_file = name ^ ".go" in - generate_file ~rendered ~destdir ~output_file + generate_file ~rendered ~destdir ~output_file:(name ^ ".go") ) objects diff --git a/ocaml/sdk-gen/go/test_data/api_versions.go b/ocaml/sdk-gen/go/test_data/api_versions.go new file mode 100644 index 00000000000..b82411d5413 --- /dev/null +++ b/ocaml/sdk-gen/go/test_data/api_versions.go @@ -0,0 +1,103 @@ +type APIVersion int + +const ( + // XenServer 4.0 (rio) + APIVersion1_1 APIVersion = iota + 1 + // XenServer 4.1 (miami) + APIVersion1_2 + APIVersionLatest APIVersion = 2 + APIVersionUnknown APIVersion = 99 +) + +func (v APIVersion) String() string { + switch v { + case APIVersion1_1: + return "1.1" + case APIVersion1_2: + return "1.2" + case APIVersionUnknown: + return "Unknown" + default: + return "Unknown" + } +} + +var APIVersionMap = map[string]APIVersion{ + // + "APIVersion1_1": APIVersion1_1, + // + "APIVersion1_2": APIVersion1_2, + // + "APIVersionLatest": APIVersionLatest, + // + "APIVersionUnknown": APIVersionUnknown, +} + +func GetAPIVersion(major int, minor int) APIVersion { + versionName := fmt.Sprintf("APIVersion%d_%d", major, minor) + apiVersion, ok := APIVersionMap[versionName] + if !ok { + apiVersion = APIVersionUnknown + } + + return apiVersion +} + +func getPoolMaster(session *Session) (HostRef, error) { + var master HostRef + poolRefs, err := Pool.GetAll(session) + if err != nil { + return master, err + } + if len(poolRefs) > 0 { + poolRecord, err := Pool.GetRecord(session, poolRefs[0]) + if err != nil { + return master, err + } + return poolRecord.Master, nil + } + return master, errors.New("pool master not found") +} + +func setSessionDetails(session *Session) error { + err := setAPIVersion(session) + if err != nil { + return err + } + err = setXAPIVersion(session) + if err != nil { + return err + } + return nil +} + +func setAPIVersion(session *Session) error { + session.APIVersion = APIVersionUnknown + masterRef, err := getPoolMaster(session) + if err != nil { + return err + } + hostRecord, err := Host.GetRecord(session, masterRef) + if err != nil { + return err + } + session.APIVersion = GetAPIVersion(hostRecord.APIVersionMajor, hostRecord.APIVersionMinor) + return nil +} + +func setXAPIVersion(session *Session) error { + masterRef, err := getPoolMaster(session) + if err != nil { + return err + } + hostRecord, err := Host.GetRecord(session, masterRef) + if err != nil { + return err + } + version, ok := hostRecord.SoftwareVersion["xapi"] + if !ok { + return errors.New("xapi version not found") + } + session.XAPIVersion = version + return nil +} \ No newline at end of file diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index d8866f901c7..39e05547f26 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -335,6 +335,18 @@ let verify_simple_convert_member = function | _ -> false +let verify_release_member = function + | "branding", `String _ | "code_name", `String _ -> + true + | "first", `Bool _ -> + true + | "version_index", `Float _ + | "version_major", `Float _ + | "version_minor", `Float _ -> + true + | _ -> + false + let verify_simple_convert_keys = ["func_name_suffix"; "type"] let verify_simple_convert = function @@ -455,6 +467,41 @@ let verify_map_convert = function | _ -> false +let release_keys = + [ + "branding" + ; "code_name" + ; "version_major" + ; "version_minor" + ; "first" + ; "version_index" + ] + +let verify_release = function + | `O members -> + schema_check release_keys verify_release_member members + | _ -> + false + +let version_keys = + ["API_VERSION_MAJOR"; "API_VERSION_MINOR"; "latest_version_index"; "releases"] + +let verify_version_member = function + | "latest_version_index", `Float _ + | "API_VERSION_MAJOR", `Float _ + | "API_VERSION_MINOR", `Float _ -> + true + | "releases", `A releases -> + List.for_all verify_release releases + | _ -> + false + +let verify_version = function + | `O members -> + schema_check version_keys verify_version_member members + | _ -> + false + let rec string_of_json_value (value : Mustache.Json.value) : string = match value with | `Null -> @@ -920,6 +967,33 @@ let messages : Mustache.Json.t = ) ] +let api_versions : Mustache.Json.t = + `O + [ + ("latest_version_index", `Float 2.) + ; ( "releases" + , `A + [ + `O + [ + ("branding", `String "XenServer 4.0") + ; ("code_name", `String "rio") + ; ("version_major", `Float 1.) + ; ("version_minor", `Float 1.) + ; ("first", `Bool true) + ] + ; `O + [ + ("branding", `String "XenServer 4.1") + ; ("code_name", `String "miami") + ; ("version_major", `Float 1.) + ; ("version_minor", `Float 2.) + ; ("first", `Bool false) + ] + ] + ) + ] + let option = `O [ @@ -988,6 +1062,8 @@ module TemplatesTest = Generic.MakeStateless (struct let option_convert_rendered = string_of_file "option_convert.go" + let api_versions_rendered = string_of_file "api_versions.go" + let option_rendered = "type OptionString *string" let tests = @@ -1016,6 +1092,7 @@ module TemplatesTest = Generic.MakeStateless (struct ; (("ConvertEnum.mustache", enum_convert), enum_convert_rendered) ; (("ConvertBatch.mustache", Convert.event_batch), batch_convert_rendered) ; (("ConvertOption.mustache", option_convert), option_convert_rendered) + ; (("APIVersions.mustache", api_versions), api_versions_rendered) ; (("Option.mustache", option), option_rendered) ] end) @@ -1036,11 +1113,14 @@ module TestGeneratedJson = struct verify "errors_and_msgs" verify_msgs_or_errors (Json.api_errors @ Json.api_messages) + let test_versions () = verify "versions" verify_version json_releases + let tests = [ ("enums", `Quick, test_enums) ; ("objs", `Quick, test_obj) ; ("errors_and_msgs", `Quick, test_errors_and_msgs) + ; ("versions", `Quick, test_versions) ] end @@ -1201,7 +1281,7 @@ module StringOfTyWithEnumsTest = struct let test_option () = let ty, enums = Json.string_of_ty_with_enums (Option String) in - verify "option" verify_string (ty, enums) + verify "option" verify_option (ty, enums) let verify_map (ty, enums) = ty = "map[int]UpdateSync" From e2a4919c5acbf1edd22078a4f7e30d246b5f2f6d Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Sat, 11 May 2024 14:00:10 +0800 Subject: [PATCH 12/12] CP-48855: remove go lint var-naming warnings Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_helper.ml | 49 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 5bf74d0864d..0a8d3740b92 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -15,16 +15,41 @@ open Datamodel_types open CommonFunctions +module Types = Datamodel_utils.Types +module StringSet = Set.Make (String) let templates_dir = "templates" let ( // ) = Filename.concat +let acronyms = + [ + "id" + ; "ip" + ; "vm" + ; "api" + ; "uuid" + ; "cpu" + ; "tls" + ; "https" + ; "url" + ; "db" + ; "xml" + ; "eof" + ] + |> StringSet.of_list + +let is_acronym word = StringSet.mem word acronyms + let snake_to_camel (s : string) : string = Astring.String.cuts ~sep:"_" s - |> List.map (fun s -> Astring.String.cuts ~sep:"-" s) - |> List.concat - |> List.map String.capitalize_ascii + |> List.concat_map (fun s -> Astring.String.cuts ~sep:"-" s) + |> List.map (function + | s when is_acronym s -> + String.uppercase_ascii s + | s -> + String.capitalize_ascii s + ) |> String.concat "" let records = @@ -386,28 +411,22 @@ module Json = struct objs let of_api_message_or_error info = - let snake_to_camel (s : string) : string = + let xapi_constants_renaming (s : string) : string = String.split_on_char '_' s |> List.map (fun seg -> let lower = String.lowercase_ascii seg in match lower with - | "vm" - | "cpu" - | "tls" - | "xml" - | "url" - | "id" - | "uuid" - | "ip" - | "api" - | "eof" -> + | s when is_acronym s -> String.uppercase_ascii lower | _ -> String.capitalize_ascii lower ) |> String.concat "" in - `O [("name", `String (snake_to_camel info)); ("value", `String info)] + `O + [ + ("name", `String (xapi_constants_renaming info)); ("value", `String info) + ] let api_messages = List.map (fun (msg, _) -> of_api_message_or_error msg) !Api_messages.msgList