Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CP-47354, CP-47355: add mustache template for messages and generate GO methods code #5591

Merged
merged 6 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion ocaml/sdk-gen/go/autogen/src/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module go/xenapi

go 1.22.0
go 1.22.2
22 changes: 18 additions & 4 deletions ocaml/sdk-gen/go/gen_go_binding.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,24 @@ let main destdir =
render_template "FileHeader.mustache" obj ~newline:true ()
in
let options_rendered = render_template "Option.mustache" obj () in
let record_rendered = render_template "Record.mustache" obj () in
let rendered = header_rendered ^ options_rendered ^ record_rendered in
let output_file = name ^ ".go" in
generate_file ~rendered ~destdir ~output_file
let record_rendered =
render_template "Record.mustache" obj () ~newline:true
in
let methods_rendered =
if name = "session" then
render_template "SessionMethod.mustache" obj ()
else
render_template "Methods.mustache" obj ()
in
let rendered =
let rendered =
[header_rendered; options_rendered; record_rendered; methods_rendered]
|> String.concat ""
|> String.trim
in
rendered ^ "\n"
in
generate_file ~rendered ~destdir ~output_file:(name ^ ".go")
)
objects

Expand Down
157 changes: 148 additions & 9 deletions ocaml/sdk-gen/go/gen_go_helper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ let render_template template_file json ?(newline = false) () =
let templ =
string_of_file (templates_dir // template_file) |> Mustache.of_string
in
let renndered = Mustache.render templ json in
let renndered = Mustache.render ~strict:true templ json in
if newline then renndered ^ "\n" else renndered

let generate_file ~rendered ~destdir ~output_file =
Expand All @@ -79,7 +79,8 @@ module Json = struct
let merge_maps m maps =
List.fold_left (fun acc map -> StringMap.union choose_enum acc map) m maps

let rec func_name_suffix = function
let rec suffix_of_type ty =
match ty with
| SecretString | String ->
"String"
| Int ->
Expand All @@ -93,17 +94,17 @@ module Json = struct
| Enum (name, _) ->
"Enum" ^ snake_to_camel name
| Set ty ->
func_name_suffix ty ^ "Set"
suffix_of_type ty ^ "Set"
| Map (ty1, ty2) ->
let k_suffix = func_name_suffix ty1 in
let v_suffix = func_name_suffix ty2 in
let k_suffix = suffix_of_type ty1 in
let v_suffix = suffix_of_type ty2 in
k_suffix ^ "To" ^ v_suffix ^ "Map"
| Ref r ->
snake_to_camel r ^ "Ref"
| Record r ->
snake_to_camel r ^ "Record"
duobei marked this conversation as resolved.
Show resolved Hide resolved
| Option ty ->
func_name_suffix ty
suffix_of_type ty

let rec string_of_ty_with_enums ty : string * enums =
match ty with
Expand Down Expand Up @@ -134,7 +135,7 @@ module Json = struct
(snake_to_camel r ^ "Record", StringMap.empty)
| Option ty ->
let _, e = string_of_ty_with_enums ty in
let name = func_name_suffix ty in
let name = suffix_of_type ty in
("Option" ^ name, e)

let of_enum name vs =
Expand Down Expand Up @@ -221,12 +222,149 @@ module Json = struct
| _ ->
[("event", `Null); ("session", `Null)]

let of_result obj msg =
match msg.msg_result with
| None ->
`Null
| Some (t, _d) ->
if obj.name = "event" && String.lowercase_ascii msg.msg_name = "from"
duobei marked this conversation as resolved.
Show resolved Hide resolved
then
`O
[
("type", `String "EventBatch")
; ("func_name_suffix", `String "EventBatch")
]
else
let t', _ = string_of_ty_with_enums t in
`O
[
("type", `String t')
; ("func_name_suffix", `String (suffix_of_type t))
]

let of_params params =
let name_internal name =
let name = name |> snake_to_camel |> String.uncapitalize_ascii in
match name with "type" -> "typeKey" | "interface" -> "inter" | _ -> name
in
let of_param param =
let suffix_of_type = suffix_of_type param.param_type in
let t, _e = string_of_ty_with_enums param.param_type in
let name = param.param_name in
[
("is_session_id", `Bool (name = "session_id"))
; ("type", `String t)
; ("name", `String name)
; ("name_internal", `String (name_internal name))
; ("doc", `String param.param_doc)
; ("func_name_suffix", `String suffix_of_type)
]
in
(* We use ',' to seprate params in Go function, we should ignore ',' before first param,
for example `func(a type1, b type2)` is wanted rather than `func(, a type1, b type2)`.
*)
let add_first = function
| head :: rest ->
let head = `O (("first", `Bool true) :: of_param head) in
let rest =
List.map
(fun item -> `O (("first", `Bool false) :: of_param item))
rest
in
head :: rest
| [] ->
[]
in
`A (add_first params)

let of_error e = `O [("name", `String e.err_name); ("doc", `String e.err_doc)]

let of_errors = function
| [] ->
`Null
| errors ->
`A (List.map of_error errors)

let add_session_info class_name method_name =
match (class_name, method_name) with
| "session", "login_with_password"
| "session", "slave_local_login_with_password" ->
[("session_login", `Bool true); ("session_logout", `Bool false)]
| "session", "logout" | "session", "local_logout" ->
[("session_login", `Bool false); ("session_logout", `Bool true)]
| _ ->
[("session_login", `Bool false); ("session_logout", `Bool false)]

let desc_of_msg msg ctor_fields =
let ctor =
if msg.msg_tag = FromObject Make then
Printf.sprintf " The constructor args are: %s (* = non-optional)."
ctor_fields
else
""
in
match msg.msg_doc ^ ctor with
| "" ->
`Null
| desc ->
`String (String.trim desc)

let ctor_fields_of_obj obj =
Datamodel_utils.fields_of_obj obj
|> List.filter (function
| {qualifier= StaticRO | RW; _} ->
true
| _ ->
false
)
|> List.map (fun f ->
String.concat "_" f.full_name
^ if f.default_value = None then "*" else ""
)
|> String.concat ", "

let messages_of_obj obj =
let ctor_fields = ctor_fields_of_obj obj in
let params_in_msg msg =
if msg.msg_session then
session_id :: msg.msg_params
else
msg.msg_params
in
List.map
(fun msg ->
let params = params_in_msg msg |> of_params in
let base_assoc_list =
[
("method_name", `String msg.msg_name)
; ("class_name", `String obj.name)
; ("class_name_exported", `String (snake_to_camel obj.name))
; ("method_name_exported", `String (snake_to_camel msg.msg_name))
; ("description", desc_of_msg msg ctor_fields)
; ("result", of_result obj msg)
; ("params", params)
; ("errors", of_errors msg.msg_errors)
; ("has_error", `Bool (msg.msg_errors <> []))
; ("async", `Bool msg.msg_async)
]
in
(* Since the param of `session *Session` isn't needed in functions of session object,
we add a special "func_params" field for session object to ignore `session *Session`.*)
if obj.name = "session" then
`O
(("func_params", msg.msg_params |> of_params)
duobei marked this conversation as resolved.
Show resolved Hide resolved
:: (add_session_info obj.name msg.msg_name @ base_assoc_list)
)
else
`O base_assoc_list
)
obj.messages

let of_option ty =
let name, _ = string_of_ty_with_enums ty in
`O
[
("type", `String name)
; ("type_name_suffix", `String (func_name_suffix ty))
("type", `String name); ("type_name_suffix", `String (suffix_of_type ty))
]

let of_options types =
Expand Down Expand Up @@ -255,6 +393,7 @@ module Json = struct
, `A (get_event_snapshot obj.name @ List.map of_field fields)
)
; ("modules", modules)
; ("messages", `A (messages_of_obj obj))
; ("option", `A (of_options types))
]
in
Expand Down
10 changes: 10 additions & 0 deletions ocaml/sdk-gen/go/gen_go_helper.mli
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ val generate_file :
rendered:string -> destdir:string -> output_file:string -> unit

module Json : sig
type enum = (string * string) list

module StringMap : Map.S with type key = string

type enums = enum StringMap.t

val suffix_of_type : Datamodel_types.ty -> string

val string_of_ty_with_enums : Datamodel_types.ty -> string * enums

val xenapi : Datamodel_types.obj list -> (string * Mustache.Json.t) list

val all_enums : Datamodel_types.obj list -> Mustache.Json.t
Expand Down
54 changes: 54 additions & 0 deletions ocaml/sdk-gen/go/templates/Methods.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{{#messages}}
// {{method_name_exported}}:{{#description}} {{.}}{{/description}}
{{#has_error}}
//
// Errors:
{{/has_error}}
{{#errors}}
// {{name}} - {{doc}}
{{/errors}}
func ({{name_internal}}) {{method_name_exported}}({{#params}}{{#first}}session *Session{{/first}}{{^first}}, {{name_internal}} {{type}}{{/first}}{{/params}}) ({{#result}}retval {{type}}, {{/result}}err error) {
method := "{{class_name}}.{{method_name}}"
{{#params}}
{{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#first}}session.ref{{/first}}{{^first}}{{name_internal}}{{/first}})
if err != nil {
return
}
{{/params}}
{{#result}}result, err := {{/result}}{{^result}}_, err = {{/result}}session.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}})
{{#result}}
if err != nil {
return
}
retval, err = deserialize{{func_name_suffix}}(method+" -> ", result)
{{/result}}
return
}

{{#async}}
// Async{{method_name_exported}}:{{#description}} {{.}}{{/description}}
{{#has_error}}
//
// Errors:
{{/has_error}}
{{#errors}}
// {{name}} - {{doc}}
{{/errors}}
func ({{name_internal}}) Async{{method_name_exported}}({{#params}}{{#first}}session *Session{{/first}}{{^first}}, {{name_internal}} {{type}}{{/first}}{{/params}}) (retval TaskRef, err error) {
method := "Async.{{class_name}}.{{method_name}}"
{{#params}}
{{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#first}}session.ref{{/first}}{{^first}}{{name_internal}}{{/first}})
if err != nil {
return
}
{{/params}}
result, err := session.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}})
if err != nil {
return
}
retval, err = deserializeTaskRef(method+" -> ", result)
return
}

{{/async}}
{{/messages}}
64 changes: 64 additions & 0 deletions ocaml/sdk-gen/go/templates/SessionMethod.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{{#messages}}
// {{method_name_exported}}:{{#description}} {{.}}{{/description}}
{{#has_error}}
//
// Errors:
{{/has_error}}
{{#errors}}
// {{name}} - {{doc}}
{{/errors}}
func (class *Session) {{method_name_exported}}({{#func_params}}{{^first}}, {{/first}}{{name_internal}} {{type}}{{/func_params}}) ({{#result}}retval {{type}}, {{/result}}err error) {
method := "{{class_name}}.{{method_name}}"
{{#params}}
{{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#is_session_id}}class.ref{{/is_session_id}}{{^is_session_id}}{{name_internal}}{{/is_session_id}})
if err != nil {
return
}
{{/params}}
{{#result}}result, err := {{/result}}{{^result}}_, err = {{/result}}class.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}})
{{#result}}
if err != nil {
return
}
retval, err = deserialize{{func_name_suffix}}(method+" -> ", result)
{{/result}}
{{#session_login}}
if err != nil {
return
}
class.ref = retval
err = setSessionDetails(class)
{{/session_login}}
{{#session_logout}}
class.ref = ""
{{/session_logout}}
return
}

{{#async}}
// Async{{method_name_exported}}:{{#description}} {{.}}{{/description}}
{{#has_error}}
//
// Errors:
{{/has_error}}
{{#errors}}
// {{name}} - {{doc}}
{{/errors}}
func (class *Session) Async{{method_name_exported}}({{#func_params}}{{^first}}, {{/first}}{{name_internal}} {{type}}{{/func_params}}) (retval TaskRef, err error) {
method := "Async.{{class_name}}.{{method_name}}"
{{#params}}
{{name_internal}}Arg, err := serialize{{func_name_suffix}}(fmt.Sprintf("%s(%s)", method, "{{name}}"), {{#is_session_id}}class.ref{{/is_session_id}}{{^is_session_id}}{{name_internal}}{{/is_session_id}})
if err != nil {
return
}
{{/params}}
result, err := class.client.sendCall(method{{#params}}, {{name_internal}}Arg{{/params}})
if err != nil {
return
}
retval, err = deserializeTaskRef(method+" -> ", result)
return
}

{{/async}}
{{/messages}}