From becc9e0424c4b8cf4ca06abd6adeb5e4c8b0767e Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Wed, 8 May 2024 15:26:34 +0800 Subject: [PATCH 1/9] CP-47356: expose `published_release_for_param` and `compare_versions` for usage of other modules Signed-off-by: Luca Zhang --- ocaml/sdk-gen/common/CommonFunctions.ml | 10 ++++------ ocaml/sdk-gen/common/CommonFunctions.mli | 9 +++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ocaml/sdk-gen/common/CommonFunctions.ml b/ocaml/sdk-gen/common/CommonFunctions.ml index a1b613a34b8..b2db32f3c51 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.ml +++ b/ocaml/sdk-gen/common/CommonFunctions.ml @@ -110,7 +110,7 @@ let rec lifecycle_matcher milestone lifecycle = else lifecycle_matcher milestone tl -let get_published_release_for_param releases = +let published_release_for_param releases = let filtered = List.filter (fun x -> x <> "closed" && x <> "3.0.3" && x <> "debug") @@ -138,8 +138,8 @@ let get_release_branding codename = let group_params_per_release params = let same_release p1 p2 = compare_versions - (get_published_release_for_param p1.param_release.internal) - (get_published_release_for_param p2.param_release.internal) + (published_release_for_param p1.param_release.internal) + (published_release_for_param p2.param_release.internal) = 0 in let rec groupByRelease acc = function @@ -240,9 +240,7 @@ and get_deprecated_info_message message = and get_published_info_param message param = let msgRelease = get_published_release message.msg_lifecycle.transitions in - let paramRelease = - get_published_release_for_param param.param_release.internal - in + let paramRelease = published_release_for_param param.param_release.internal in if compare_versions paramRelease msgRelease > 0 then sprintf "First published in %s." (get_release_branding paramRelease) else diff --git a/ocaml/sdk-gen/common/CommonFunctions.mli b/ocaml/sdk-gen/common/CommonFunctions.mli index 197dcb8a3a4..c738f73b4a7 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.mli +++ b/ocaml/sdk-gen/common/CommonFunctions.mli @@ -137,3 +137,12 @@ val session_id : param val objects : obj list (** Objects of api that generate SDKs. *) + +val published_release_for_param : string list -> string + +val compare_versions : string -> string -> int +(** Compare two published releases. + @param published release r1. + @param published release r2. + @return the order diff between r1 and r2. +*) From bc18f0ea81a1e84200e6c6b38b1242c2af7d3076 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Sat, 11 May 2024 12:48:46 +0800 Subject: [PATCH 2/9] CP-47356: Support backwards capability for Go SDK Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_helper.ml | 216 ++++++++++------ ocaml/sdk-gen/go/gen_go_helper.mli | 3 + ocaml/sdk-gen/go/templates/Methods.mustache | 2 + .../go/templates/SessionMethod.mustache | 2 + ocaml/sdk-gen/go/test_data/methods.go | 2 + ocaml/sdk-gen/go/test_data/session_method.go | 2 + ocaml/sdk-gen/go/test_gen_go.ml | 232 +++++++++++++++++- 7 files changed, 383 insertions(+), 76 deletions(-) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 4a9623550a9..7bc83de9c75 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -242,40 +242,95 @@ module Json = struct ; ("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 + let group_params msg = + let published_release releases = + match (published_release_for_param releases, releases) with + | "", [rel] -> + rel + | rel, _ -> + rel 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) - ] + let do_group params = + let params = + List.map + (fun p -> (published_release p.param_release.internal, p)) + params + in + let uniq_sorted_rels = + List.map fst params + |> Listext.List.setify + |> List.fast_sort compare_versions + in + List.map + (fun rel -> + let params = + List.filter_map + (fun (r, param) -> + match compare_versions r rel with + | n when n > 0 -> + None + | _ -> + Some param + ) + params + in + (params, rel) + ) + uniq_sorted_rels + in + let only_session_group = + [([session_id], published_release msg.msg_release.internal)] 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 groups = + match (msg.msg_session, do_group msg.msg_params) with + | true, [] -> + only_session_group + | true, groups -> + List.map (fun (params, rel) -> (session_id :: params, rel)) groups + | false, groups -> + groups + in + (* The bool label in the tuple below is tagged to distinguish whether it is the latest parameters group. + If it's the latest parameters group, then we should not add the number of parameters to the method's name. *) - 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 - | [] -> - [] + match List.rev groups with + | (params, rel) :: _ as groups -> + (true, params, rel) + :: List.map (fun (params, rel) -> (false, params, rel)) groups + | [] -> + failwith "Empty params group should not exist." + + let of_param param = + let name_internal name = + let name = name |> snake_to_camel |> String.uncapitalize_ascii in + match name with "type" -> "typeKey" | "interface" -> "inter" | _ -> name in - `A (add_first params) + 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) + ] + + (* 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 of_params = 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 + | [] -> + [] let of_error e = `O [("name", `String e.err_name); ("doc", `String e.err_doc)] @@ -285,16 +340,6 @@ module Json = struct | 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 @@ -323,42 +368,65 @@ module Json = struct ) |> String.concat ", " + let method_name_exported method_name params latest = + let method_name = snake_to_camel method_name in + if latest then + method_name + else + method_name ^ string_of_int (List.length params) + + (* 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`.*) + let addtion_info_of_session method_name params latest = + let add_session_info method_name = + match method_name with + | "login_with_password" | "slave_local_login_with_password" -> + [("session_login", `Bool true); ("session_logout", `Bool false)] + | "logout" | "local_logout" -> + [("session_login", `Bool false); ("session_logout", `Bool true)] + | _ -> + [("session_login", `Bool false); ("session_logout", `Bool false)] + in + let name = method_name_exported method_name params latest in + ("func_params", `A (of_params params)) + :: ("method_name_exported", `String name) + :: add_session_info method_name + + let addtion_info msg params latest = + let method_name = msg.msg_name in + match (String.lowercase_ascii msg.msg_obj_name, msg.msg_session) with + | "session", true -> + addtion_info_of_session method_name (List.tl params) latest + | "session", false -> + addtion_info_of_session method_name params latest + | _ -> + let name = method_name_exported method_name params latest in + [("method_name_exported", `String name)] + 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 + obj.messages + |> List.rev_map (fun msg -> + let of_message (latest, params, rel_version) = + let base_assoc_list = + [ + ("method_name", `String msg.msg_name) + ; ("class_name", `String obj.name) + ; ("class_name_exported", `String (snake_to_camel obj.name)) + ; ("description", desc_of_msg msg ctor_fields) + ; ("result", of_result obj msg) + ; ("params", `A (of_params params)) + ; ("errors", of_errors msg.msg_errors) + ; ("has_error", `Bool (msg.msg_errors <> [])) + ; ("async", `Bool msg.msg_async) + ; ("version", `String rel_version) + ] + in + `O (base_assoc_list @ addtion_info msg params latest) + in + msg |> group_params |> List.map of_message + ) + |> List.concat let of_option ty = let name, _ = string_of_ty_with_enums ty in diff --git a/ocaml/sdk-gen/go/gen_go_helper.mli b/ocaml/sdk-gen/go/gen_go_helper.mli index 85dff4ba4d4..d5de1050a05 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.mli +++ b/ocaml/sdk-gen/go/gen_go_helper.mli @@ -32,6 +32,9 @@ module Json : sig val string_of_ty_with_enums : Datamodel_types.ty -> string * enums + val group_params : + Datamodel_types.message -> (bool * Datamodel_types.param list * string) list + 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/templates/Methods.mustache b/ocaml/sdk-gen/go/templates/Methods.mustache index 32a0a5c8982..384b6949499 100644 --- a/ocaml/sdk-gen/go/templates/Methods.mustache +++ b/ocaml/sdk-gen/go/templates/Methods.mustache @@ -1,5 +1,6 @@ {{#messages}} // {{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#version}}// Version: {{.}}{{/version}} {{#has_error}} // // Errors: @@ -27,6 +28,7 @@ func ({{name_internal}}) {{method_name_exported}}({{#params}}{{#first}}session * {{#async}} // Async{{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#version}}// Version: {{.}}{{/version}} {{#has_error}} // // Errors: diff --git a/ocaml/sdk-gen/go/templates/SessionMethod.mustache b/ocaml/sdk-gen/go/templates/SessionMethod.mustache index b73fb057379..7613c69e75f 100644 --- a/ocaml/sdk-gen/go/templates/SessionMethod.mustache +++ b/ocaml/sdk-gen/go/templates/SessionMethod.mustache @@ -1,5 +1,6 @@ {{#messages}} // {{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#version}}// Version: {{.}}{{/version}} {{#has_error}} // // Errors: @@ -37,6 +38,7 @@ func (class *Session) {{method_name_exported}}({{#func_params}}{{^first}}, {{/fi {{#async}} // Async{{method_name_exported}}:{{#description}} {{.}}{{/description}} +{{#version}}// Version: {{.}}{{/version}} {{#has_error}} // // Errors: diff --git a/ocaml/sdk-gen/go/test_data/methods.go b/ocaml/sdk-gen/go/test_data/methods.go index 3e80d71b0f7..fd268ba83bb 100644 --- a/ocaml/sdk-gen/go/test_data/methods.go +++ b/ocaml/sdk-gen/go/test_data/methods.go @@ -1,4 +1,5 @@ // GetLog: GetLog Get the host log file +// Version: miami 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) @@ -18,6 +19,7 @@ func (host) GetLog(session *Session, host HostRef) (retval string, err error) { } // AsyncGetLog: GetLog Get the host log file +// Version: miami 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) diff --git a/ocaml/sdk-gen/go/test_data/session_method.go b/ocaml/sdk-gen/go/test_data/session_method.go index b476f4606a8..cda84de9ad6 100644 --- a/ocaml/sdk-gen/go/test_data/session_method.go +++ b/ocaml/sdk-gen/go/test_data/session_method.go @@ -1,4 +1,5 @@ // LoginWithPassword: Attempt to authenticate the user); returning a session reference if successful +// Version: miami // // Errors: // SESSION_AUTHENTICATION_FAILED - The credentials given by the user are incorrect @@ -26,6 +27,7 @@ func (class *Session) LoginWithPassword(uname string, pwd string) (retval Sessio } // Logout: Logout Log out of a session +// Version: miami func (class *Session) Logout() (err error) { method := "session.logout" sessionIDArg, err := serializeSessionRef(fmt.Sprintf("%s(%s)", method, "session_id"), class.ref) diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 4767b0c05ec..ef484cf0622 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -120,7 +120,8 @@ let verify_message_member = function | "method_name", `String _ | "class_name", `String _ | "class_name_exported", `String _ - | "method_name_exported", `String _ -> + | "method_name_exported", `String _ + | "version", `String _ -> true | "description", `String _ | "description", `Null -> true @@ -141,7 +142,8 @@ let verify_sesseion_message_member = function | "method_name", `String _ | "class_name", `String _ | "class_name_exported", `String _ - | "method_name_exported", `String _ -> + | "method_name_exported", `String _ + | "version", `String _ -> true | "description", `String _ | "description", `Null -> true @@ -176,6 +178,7 @@ let message_keys = ; "errors" ; "has_error" ; "async" + ; "version" ] let session_message_keys = @@ -589,6 +592,7 @@ let session_messages : Mustache.Json.t = reference if successful" ) ; ("async", `Bool false) + ; ("version", `String "miami") ; ( "func_params" , `A [ @@ -666,6 +670,7 @@ let session_messages : Mustache.Json.t = ; ("description", `String "Logout Log out of a session") ; ("async", `Bool false) ; ("func_params", `A []) + ; ("version", `String "miami") ; ( "params" , `A [ @@ -701,6 +706,7 @@ let messages : Mustache.Json.t = ; ("method_name_exported", `String "GetLog") ; ("description", `String "GetLog Get the host log file") ; ("async", `Bool true) + ; ("version", `String "miami") ; ( "params" , `A [ @@ -934,6 +940,227 @@ module StringOfTyWithEnumsTest = struct ] end +module GroupParamsTest = Generic.MakeStateless (struct + open Datamodel_types + open Datamodel_common + + let string_of_message msg = msg.msg_obj_name ^ "." ^ msg.msg_name + + let string_of_param param = param.param_name ^ ":" ^ param.param_doc + + let string_of_group (latest, params, rel_version) = + Printf.sprintf "(latest = %b, release_version = %s, params = [%s])" latest + rel_version + (Test_printers.list string_of_param params) + + let string_of_groups groups = + Printf.sprintf "param_groups : [%s]}" + (Test_printers.list string_of_group groups) + + module Io = struct + type input_t = message + + type output_t = ((bool * param list * string) list, string) result + + let string_of_input_t = string_of_message + + let string_of_output_t = function + | Ok groups -> + Fmt.(str "%a" Dump.string) (string_of_groups groups) + | Error e -> + Fmt.(str "%a" Dump.string) e + end + + let transform message = + try Ok (Json.group_params message) with Failure e -> Error e + + let network = + { + param_type= Ref _network + ; param_name= "network" + ; param_doc= "Network to add the bonded PIF to" + ; param_release= miami_release + ; param_default= None + } + + let members = + { + param_type= Set (Ref _pif) + ; param_name= "members" + ; param_doc= "PIFs to add to this bond" + ; param_release= miami_release + ; param_default= None + } + + let mac = + { + param_type= String + ; param_name= "MAC" + ; param_doc= "The MAC address to use on the bond itself." + ; param_release= miami_release + ; param_default= None + } + + let mode = + { + param_type= Enum ("bond_mode", [("balance-slb", "Source-level balancing")]) + ; param_name= "mode" + ; param_doc= "Bonding mode to use for the new bond" + ; param_release= boston_release + ; param_default= Some (VEnum "balance-slb") + } + + let properties = + { + param_type= Map (String, String) + ; param_name= "properties" + ; param_doc= "Additional configuration parameters specific to the bond mode" + ; param_release= tampa_release + ; param_default= Some (VMap []) + } + + let num_release = numbered_release "1.250.0" + + let numbered_release_param = + { + param_type= String + ; param_name= "param" + ; param_doc= "A parm for testing" + ; param_release= num_release + ; param_default= None + } + + let group1 = [network; members; mac] + + let group2 = group1 @ [mode] + + let group3 = group2 @ [properties] + + let group4 = group3 @ [numbered_release_param] + + let msg_with_session = + { + msg_name= "create" + ; msg_params= group4 + ; msg_result= Some (Ref "Bond", "The reference of the created Bond object") + ; msg_errors= [] + ; msg_doc= "Create an interface bond" + ; msg_async= true + ; msg_session= true + ; msg_secret= false + ; msg_pool_internal= false + ; msg_db_only= false + ; msg_release= miami_release + ; msg_lifecycle= Lifecycle.from [] + ; msg_has_effect= true + ; msg_force_custom= None + ; msg_no_current_operations= false + ; msg_tag= Custom + ; msg_obj_name= "Bond" + ; msg_custom_marshaller= false + ; msg_hide_from_docs= false + ; msg_allowed_roles= Some ["pool-admin"; "pool-operator"] + ; msg_map_keys_roles= [] + ; msg_doc_tags= [] + ; msg_forward_to= None + } + + let num_version = published_release_for_param num_release.internal + + let msg_with_session_expected = + [ + (true, session_id :: group4, num_version) + ; (false, session_id :: group4, num_version) + ; (false, session_id :: group3, rel_tampa) + ; (false, session_id :: group2, rel_boston) + ; (false, session_id :: group1, rel_miami) + ] + + let msg_with_session_with_only_param = + {msg_with_session with msg_params= [network]} + + let msg_with_session_with_only_param_expected = + [ + (true, [session_id; network], rel_miami) + ; (false, [session_id; network], rel_miami) + ] + + let msg_without_session = {msg_with_session with msg_session= false} + + let msg_without_session_expected = + [ + (true, group4, num_version) + ; (false, group4, num_version) + ; (false, group3, rel_tampa) + ; (false, group2, rel_boston) + ; (false, group1, rel_miami) + ] + + let msg_without_session_with_only_param = + {msg_with_session with msg_session= false; msg_params= [network]} + + let msg_without_session_with_only_param_expected = + [(true, [network], rel_miami); (false, [network], rel_miami)] + + (*Message has session param, but has no other params.*) + let msg_with_session_without_param = {msg_with_session with msg_params= []} + + let msg_with_session_without_param_expected = + let version = + published_release_for_param + msg_with_session_without_param.msg_release.internal + in + [(true, [session_id], version); (false, [session_id], version)] + + let msg_without_session_without_param = + {msg_with_session with msg_params= []; msg_session= false} + + (*Message which in session object has not session param and has no other params.*) + let msg_with_session_without_param_in_session_object = + {msg_with_session with msg_params= []; msg_obj_name= "Session"} + + let msg_with_session_without_param_in_session_object_expected = + let version = + published_release_for_param + msg_with_session_without_param_in_session_object.msg_release.internal + in + [(true, [session_id], version); (false, [session_id], version)] + + (*Message which in session object has not session param and has no other params.*) + let msg_without_session_without_param_in_session_object = + { + msg_with_session with + msg_params= [] + ; msg_obj_name= "Session" + ; msg_session= false + } + + let tests = + `QuickAndAutoDocumented + [ + (msg_with_session, Ok msg_with_session_expected) + ; ( msg_with_session_with_only_param + , Ok msg_with_session_with_only_param_expected + ) + ; (msg_without_session, Ok msg_without_session_expected) + ; ( msg_without_session_with_only_param + , Ok msg_without_session_with_only_param_expected + ) + ; ( msg_with_session_without_param + , Ok msg_with_session_without_param_expected + ) + ; ( msg_without_session_without_param + , Error "Empty params group should not exist." + ) + ; ( msg_with_session_without_param_in_session_object + , Ok msg_with_session_without_param_in_session_object_expected + ) + ; ( msg_without_session_without_param_in_session_object + , Error "Empty params group should not exist." + ) + ] +end) + let tests = make_suite "gen_go_binding_" [ @@ -942,6 +1169,7 @@ let tests = ; ("string_of_ty_with_enums", StringOfTyWithEnumsTest.tests) ; ("templates", TemplatesTest.tests) ; ("generated_mustache_jsons", TestGeneratedJson.tests) + ; ("group_params", GroupParamsTest.tests) ] let () = Alcotest.run "Gen go binding" tests From ed99de9f128cc881bd9ba9d7f000b13a56c202b9 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 13:56:15 +0800 Subject: [PATCH 3/9] 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 90b9cfcb9d0dce3b3e9891a724257c8b62be6d58 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 14:20:01 +0800 Subject: [PATCH 4/9] 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 | 277 ++++++++++++++++++++++- ocaml/sdk-gen/go/gen_go_helper.mli | 69 ++++++ 5 files changed, 455 insertions(+), 1 deletion(-) diff --git a/ocaml/sdk-gen/common/CommonFunctions.ml b/ocaml/sdk-gen/common/CommonFunctions.ml index b2db32f3c51..5f1b5b3a560 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.ml +++ b/ocaml/sdk-gen/common/CommonFunctions.ml @@ -361,3 +361,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 c738f73b4a7..a927c5400ec 100644 --- a/ocaml/sdk-gen/common/CommonFunctions.mli +++ b/ocaml/sdk-gen/common/CommonFunctions.mli @@ -146,3 +146,11 @@ val compare_versions : string -> string -> int @param published release r2. @return the order diff between r1 and r2. *) + +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 b12077a7593..0203e16966c 100644 --- a/ocaml/sdk-gen/go/gen_go_binding.ml +++ b/ocaml/sdk-gen/go/gen_go_binding.ml @@ -68,11 +68,60 @@ 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_versions 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 7bc83de9c75..0f9760dbdab 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -52,6 +52,14 @@ let snake_to_camel (s : string) : string = ) |> 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 @@ -183,7 +191,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 @@ -493,3 +501,270 @@ module Json = struct let api_errors = List.map of_api_message_or_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.suffix_of_type 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.suffix_of_type ty; value_ty= name; items} + | Set ty as set -> + let fp_ty = Json.suffix_of_type ty in + let ty, _ = Json.string_of_ty_with_enums ty in + Set + { + func_suffix= Json.suffix_of_type 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.suffix_of_type ty + ; value_ty= name + ; key_ty= Json.suffix_of_type ty1 + ; val_ty= Json.suffix_of_type ty2 + } + | Ref _ as ty -> + 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 + 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.suffix_of_type 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 d5de1050a05..4d8a3691f2e 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.mli +++ b/ocaml/sdk-gen/go/gen_go_helper.mli @@ -43,3 +43,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 29a64d26a6030092d9d053d956fcc12ce400bf32 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 20:10:07 +0800 Subject: [PATCH 5/9] 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 ef484cf0622..44b9db8b57a 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -344,6 +344,12 @@ let verify_release_member = function | _ -> false +let verify_simple_convert_member = function + | "func_name_suffix", `String _ | "type", `String _ -> + true + | _ -> + false + let release_keys = [ "branding" @@ -379,6 +385,126 @@ let verify_version = function | _ -> 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 -> @@ -744,6 +870,151 @@ let 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 @@ -777,6 +1048,30 @@ module TemplatesTest = Generic.MakeStateless (struct let option_rendered = "type OptionString *string" + 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 [ @@ -789,6 +1084,22 @@ module TemplatesTest = Generic.MakeStateless (struct ; (("APIMessages.mustache", api_messages), api_messages_rendered) ; (("APIVersions.mustache", api_versions), api_versions_rendered) ; (("Option.mustache", option), option_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) @@ -1161,6 +1472,69 @@ module GroupParamsTest = Generic.MakeStateless (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_" [ @@ -1170,6 +1544,7 @@ let tests = ; ("templates", TemplatesTest.tests) ; ("generated_mustache_jsons", TestGeneratedJson.tests) ; ("group_params", GroupParamsTest.tests) + ; ("generated_convert_jsons", TestConvertGeneratedJson.tests) ] let () = Alcotest.run "Gen go binding" tests From 041fc68fa64053484aa3b559c7eaf9df2dffb342 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Wed, 24 Apr 2024 09:56:36 +0800 Subject: [PATCH 6/9] CP-47354: Generate messages functions Golang code for all classes Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/test_gen_go.ml | 292 ++++++++++++++++---------------- 1 file changed, 145 insertions(+), 147 deletions(-) diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 44b9db8b57a..0b4bd5a8dea 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -698,6 +698,151 @@ let option = ) ] +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)] + let session_messages : Mustache.Json.t = `O [ @@ -842,8 +987,6 @@ 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 @@ -870,151 +1013,6 @@ let 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 From 2e871a189507952e1ecc232df90987dd1338f8a9 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Mon, 6 May 2024 20:15:36 +0800 Subject: [PATCH 7/9] 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 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 0b4bd5a8dea..34d20b79621 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -987,6 +987,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 From f2c02d8ed461dced0c4b35c9fafd42bb024864c2 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 22:15:34 +0800 Subject: [PATCH 8/9] CP-48855: render options Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_helper.ml | 12 ++++ ocaml/sdk-gen/go/test_gen_go.ml | 107 ++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index 0f9760dbdab..e6608a21c43 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -448,6 +448,18 @@ module Json = struct |> List.filter_map (function Option ty -> Some ty | _ -> None) |> List.map of_option + 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 -> diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 34d20b79621..7b44285deb7 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -1015,6 +1015,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 @@ -1072,6 +1087,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 [ @@ -1100,6 +1117,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) @@ -1535,6 +1553,95 @@ 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 "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 "option" 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_" [ From 2e5635f3919e328493ff26d4a02c7f27f5ad6d91 Mon Sep 17 00:00:00 2001 From: Luca Zhang Date: Tue, 30 Apr 2024 22:26:09 +0800 Subject: [PATCH 9/9] CP-48855: render APIVersion Signed-off-by: Luca Zhang --- ocaml/sdk-gen/go/gen_go_helper.ml | 12 -- ocaml/sdk-gen/go/test_gen_go.ml | 245 +++++++++--------------------- 2 files changed, 70 insertions(+), 187 deletions(-) diff --git a/ocaml/sdk-gen/go/gen_go_helper.ml b/ocaml/sdk-gen/go/gen_go_helper.ml index e6608a21c43..0f9760dbdab 100644 --- a/ocaml/sdk-gen/go/gen_go_helper.ml +++ b/ocaml/sdk-gen/go/gen_go_helper.ml @@ -448,18 +448,6 @@ module Json = struct |> List.filter_map (function Option ty -> Some ty | _ -> None) |> List.map of_option - 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 -> diff --git a/ocaml/sdk-gen/go/test_gen_go.ml b/ocaml/sdk-gen/go/test_gen_go.ml index 7b44285deb7..02ee17cb2b7 100644 --- a/ocaml/sdk-gen/go/test_gen_go.ml +++ b/ocaml/sdk-gen/go/test_gen_go.ml @@ -332,6 +332,12 @@ 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_release_member = function | "branding", `String _ | "code_name", `String _ -> true @@ -344,47 +350,6 @@ let verify_release_member = function | _ -> false -let verify_simple_convert_member = function - | "func_name_suffix", `String _ | "type", `String _ -> - true - | _ -> - 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 verify_simple_convert_keys = ["func_name_suffix"; "type"] let verify_simple_convert = function @@ -505,6 +470,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 -> @@ -656,48 +656,6 @@ let api_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 - [ - ( "option" - , `A - [ - `O - [ - ("type", `String "string") - ; ("type_name_suffix", `String "String") - ] - ] - ) - ] - let simple_type_convert : Mustache.Json.t = let array = [ @@ -1015,6 +973,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 [ @@ -1087,8 +1072,6 @@ module TemplatesTest = Generic.MakeStateless (struct let option_convert_rendered = string_of_file "option_convert.go" - let option_rendered = "type OptionString *string" - let tests = `QuickAndAutoDocumented [ @@ -1117,6 +1100,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) @@ -1180,95 +1164,6 @@ module SuffixOfTypeTest = Generic.MakeStateless (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 "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 "option" verify_option (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 - module GroupParamsTest = Generic.MakeStateless (struct open Datamodel_types open Datamodel_common @@ -1615,7 +1510,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"