Search code examples
ocaml

Unbound record field api_key in my OCaml code


I keep getting the below error when I run dune build:

File "lib/ip2locationio.ml", line 25, characters 73-80:
25 |     let uri = Uri.of_string ("https://api.ip2location.io/?key=" ^ config.api_key ^ "&format=" ^ config.format ^ "&source=" ^ config.source ^ "&source_version=" ^ config.source_version ^ "&ip=" ^ ip ^ lang_str) in
                                                                              ^^^^^^^
Error: Unbound record field api_key

I have looked through the other posts with similar issues and I've provided the signature in the .mli file but still unable to fix the issue.

My ip2locationio.ml file

open Lwt
open Cohttp
open Cohttp_lwt_unix
open Yojson

module Configuration = struct
  type config = {
    source_version : string;
    api_key : string;
    format: string;
    source: string
  }
  
  let init api_key = {
    source_version = "1.0.0";
    api_key = api_key;
    format = "json";
    source = "sdk-ocaml-iplio"
  }
end

module Ip_geolocation = struct
  let call_api config ip lang =
    let lang_str = if lang == "" then "" else "&lang=" ^ lang in
    let uri = Uri.of_string ("https://api.ip2location.io/?key=" ^ config.api_key ^ "&format=" ^ config.format ^ "&source=" ^ config.source ^ "&source_version=" ^ config.source_version ^ "&ip=" ^ ip ^ lang_str) in
    
    Lwt_main.run begin
      Client.get uri >>= fun (resp, body) ->
        let code = resp |> Response.status |> Code.code_of_status in
        let json_promise = body |> Cohttp_lwt.Body.to_string in
        json_promise >>= (fun json_string ->
          return (code, json_string)
        )
    end
  
  (** Call the API to get geolocation info *)
  let lookup config ip lang =
    let code, json_string = call_api config ip lang in
    let json = Basic.from_string json_string in
    (code, json)
end

My ip2locationio.mli file

module Configuration :
  sig
    type config = {
      source_version : string;
      api_key : string;
      format: string;
      source: string;
    }
    val init : string -> config
  end
module Ip_geolocation :
  sig
    val lookup :
      Configuration.config -> string -> string -> int * Yojson.Basic.t
  end

I would be very grateful if anyone can point me in the right direction. I am not sure what else I'm missing.


Solution

  • Let's consider a simpler example that replicates your error.

    # module A = struct
        type t = {x : int}
      end;;
    module A : sig type t = { x : int; } end
    # module B = struct
        let foo c = c.x
      end;;
    Error: Unbound record field x
    # module C = struct
        let foo (c : A.t) = c.x
      end;;
    module C : sig val foo : A.t -> int end
    

    Attempting to create a new A.t value runs into similar issues.

    # A.{x = 5};;
    - : A.t = {A.x = 5}
    # {A.x = 7};;
    - : A.t = {A.x = 7}
    # {x = 4};;
    Error: Unbound record field x
    

    A straightforward resolution of this issue would be to explicitly type the function argument.

      let call_api (config : Configuration.config) ip lang =
    

    As a sidenote, consider the following line from your code:

        let lang_str = if lang == "" then "" else "&lang=" ^ lang in
    

    == in OCaml checks for physical equality: are these two values literally the same value? However, = checks for structural equality: are these two values equal?

    You likely wanted:

        let lang_str = if lang = "" then "" else "&lang=" ^ lang in
    

    You may also find it advantageous for readability to use Printf.sprintf in place of numerous string concatenations.

    "https://api.ip2location.io/?key=" ^ config.api_key ^ "&format=" ^ config.format ^ "&source=" ^ config.source ^ "&source_version=" ^ config.source_version ^ "&ip=" ^ ip ^ lang_str
    

    Becomes:

    Printf.sprintf 
      "https://api.ip2location.io/?key=%s&format=%s&source=%s&source_version=%s&ip=%s%s"
      config.api_key config.format config.source 
      config.source_version ip lang_str