OpenAPI Generator에서 Elixir 기반 Moralis API 클라이언트 만들기

블록체인에 쉽게 액세스할 수 있는 HTTP APIMoralis » The Ultimate Web3 Development Platform 서비스를 제공합니다.그 API는 OpenAPI에 규격을 기술했다.이 글에서 나는 Elixir부터 Moralis의 API를 사용해 보려고 한다.
이번에 시도한 코드는 kentaro/elixir-moralis 위에 놓여 있다.적당히 참고하세요.

이른바 OpenAPI


OpenAPI(정확히 말하면 The OpenAPI Specification)는 HTTP 기반의 API 규격을 기술하는 데 사용되는 규격OpenAPI Specification v3.1.0 | Introduction, Definitions, & More이다.OpenAPI에 기술된 API 규격(모드.JSON과 YAML로 작성할 수 있음)을 파트너로 하여 API 문서, 클라이언트, 서버를 자동으로 생성할 수 있는 편리한 메커니즘이다.
이 글의 관심사에 따라 OpenAPI에서 기술한 규격을 바탕으로 API 클라이언트를 자동으로 생성하는 데 사용될 것입니다.여기서 생성기로 사용해 보세요openapi-generator-cli.npm 명령을 사용하여 설치openapi-generator-cli.
$ npm install @openapitools/openapi-generator-cli -g

Moralis API 사양


Moralis API의 규격은 Moralis Admin(사용자 등록 필요)로 통일됩니다.또한 https://deep-index.moralis.io/api-docs/v2/swagger.json 중 OpenAPI 3.0.0에 기술된 JSON 파일이 있어 Swagger Editor 읽으면 좋은 느낌을 준다.

API 클라이언트 라이브러리 만들기


프로젝트 준비


여기서 하고 싶은 일은 다음과 같다.
  • OpenAPI에서 기술한 모델에 따라 Elixir 기반 클라이언트 구현
  • 생성
  • 생성된 구현을 이용하여 API에 접근하는 프로그램 라이브러리 만들기
  • 후술 실행openapi-generator-cli은 클라이언트를 생성하여mix 프로젝트로 실현한다.따라서 이 실장된 구성 프로그램 라이브러리를 사용하는mix 프로젝트와 동시에 존재하는 형식으로 포장을 구성해야 한다.그곳에서는 안브레라 프로젝트 같은 구조를 사용할 수 있다.mix new--umbrella는 추가 옵션을 통해 안브레라 프로젝트로 프로젝트를 만들 수 있습니다.
    $ mix new moralis --umbrella
    * creating README.md
    * creating .formatter.exs
    * creating .gitignore
    * creating mix.exs
    * creating apps
    * creating config
    * creating config/config.exs
    
    Your umbrella project was created successfully.
    Inside your project, you will find an apps/ directory
    where you can create and host many apps:
    
        cd moralis
        cd apps
        mix new my_app
    
    Commands like "mix compile" and "mix test" when executed
    in the umbrella project root will automatically run
    for each application in the apps/ directory.
    $ mv moralis/ elixir-moralis
    $ cd elixir-moralis
    
    먼저 클라이언트 프로그램 라이브러리의 주체부터 제작한다.apps/ 제작 목록.
    $ cd apps/
    $ mix new moralis
    
    다음으로 OpenAPI에서 기술한 모델에서 API 클라이언트의 Elixir를 생성합니다.
    문서의 내용에 따라 실행하면 스펙의validation 오류가 발생합니다 Documentation for the elixir Generator
    참조--skip-validate-spec가 비활성화되면 다음 명령을 실행합니다.또한 모듈 이름openapi-generator-cli을 지정하는 것도 요점이다.
    $ openapi-generator-cli generate -i https://deep-index.moralis.io/api-docs/v2/swagger.json -g elixir -o ./gen --skip-validate-spec --invoker-package Moralis
    Did set selected version to 5.4.0
    [main] WARN  o.o.c.config.CodegenConfigurator - There were issues with the specification, but validation has been explicitly disabled.
    Errors:
    	-attribute components.schemas.trade.items is missing
    Warnings:
    	-Unused model: nftContractMetadataCollection
    	-Unused model: historicalNftTransfer
    	-Unused model: erc721Metadata
    
    (省略)
    
    ################################################################################
    # Thanks for using OpenAPI Generator.                                          #
    # Please consider donation to help us maintain this project 🙏                 #
    # https://opencollective.com/openapi_generator/donate                          #
    ################################################################################
    
    목록 구성은 이렇게 해야 한다.
    $ tree -L 2
    .
    ├── README.md
    ├── apps
    │   ├── gen
    │   ├── moralis
    │   └── openapitools.json
    ├── config
    │   └── config.exs
    └── mix.exs
    
    4 directories, 4 files
    

    생성된 코드를 보세요.


    API의 용도로 어떤 주소에 링크된 NFC 일람표를 얻으려고 합니다.그 코드 좀 봐.
    이런 문서를 만들었다Moralis.
    Gets the NFTs owned by a given address
    Gets NFTs owned by the given address * The response will include status [SYNCED/SYNCING] based on the contracts being indexed. * Use the token_address param to get results for a specific contract only * Note results will include all indexed NFTs * Any request which includes the token_address param will start the indexing process for that NFT collection the very first time it is requested 
    
    ## Parameters
    
    - connection (Moralis.Connection): Connection to server
    - address (String.t): The owner of a given token
    - opts (KeywordList): [optional] Optional parameters
      - :chain (Moralis.Model.ChainList.t): The chain to query
      - :format (String.t): The format of the token id
      - :offset (integer()): offset
      - :limit (integer()): limit
      - :token_addresses ([String.t]): The addresses to get balances for (Optional)
      - :cursor (String.t): The cursor returned in the last response (for getting the next page) 
    ## Returns
    
    {:ok, Moralis.Model.NftOwnerCollection.t} on success
    {:error, Tesla.Env.t} on failure
    
    에 대응하는 코드는 다음과 같다apps/gen/lib/moralis/api/account.ex.스펙도 좋은 정의가 있네.
    @spec get_nfts(Tesla.Env.client, String.t, keyword()) :: {:ok, Moralis.Model.NftOwnerCollection.t} | {:error, Tesla.Env.t}
    def get_nfts(connection, address, opts \\ []) do
      optional_params = %{
        :"chain" => :query,
        :"format" => :query,
        :"offset" => :query,
        :"limit" => :query,
        :"token_addresses" => :query,
        :"cursor" => :query
      }
      %{}
      |> method(:get)
      |> url("/#{address}/nft")
      |> add_optional_params(optional_params, opts)
      |> Enum.into([])
      |> (&Connection.request(connection, &1)).()
      |> evaluate_response([
        { 200, %Moralis.Model.NftOwnerCollection{}}
      ])
    end
    

    파일 다시 쓰기


    생성기에서 생성한 파일을 다시 쓸 수 없지만 apps/gen/lib/moralis/api/account.ex 오류가 발생하기 때문에 mix.exs 부분은 수동으로 다시 쓸 수 있다version 등.겸사겸사 0.0.1 의 이름 샷을 적당한 이름으로 바꾸었다.
    diff --git a/apps/gen/mix.exs b/apps/gen/mix.exs
    index 2bcb487..c38f7fc 100644
    --- a/apps/gen/mix.exs
    +++ b/apps/gen/mix.exs
    @@ -1,9 +1,9 @@
    -defmodule Moralis.Mixfile do
    +defmodule Moralis.Gen.Mixfile do
       use Mix.Project
     
       def project do
    -    [app: :moralis,
    -     version: "2",
    +    [app: :gen,
    +     version: "0.0.1",
          elixir: "~> 1.6",
          build_embedded: Mix.env == :prod,
          start_permanent: Mix.env == :prod,
    diff --git a/apps/moralis/mix.exs b/apps/moralis/mix.exs
    index a75adcf..c2d36d6 100644
    --- a/apps/moralis/mix.exs
    +++ b/apps/moralis/mix.exs
    @@ -1,4 +1,4 @@
    -defmodule Moralis.MixProject do
    +defmodule Moralis.Core.MixProject do
       use Mix.Project
     
       def project do
    

    클라이언트 모듈 만들기


    생성된 구현을 사용하여 API에 액세스하는 클라이언트 코드를 씁니다Mix.Project.생성된 구현에서 전달된apps/moralis/lib/moralis.ex을 사용하여 API에 접근하기 때문에 환경 변수에서 API 키로 설정된 클라이언트를 읽는 코드를 썼다.
    defmodule Moralis do
      @moduledoc """
      Handle Tesla connections for Moralis.
      """
    
      @doc """
      Configure an authless client connection
    
      # Returns
    
      Tesla.Env.client
      """
      @spec client() :: Tesla.Env.client
      def client do
        [
          {Tesla.Middleware.BaseUrl, "https://deep-index.moralis.io/api/v2"},
          {Tesla.Middleware.EncodeJson, engine: Poison},
          {Tesla.Middleware.Headers, [{"x-api-key", System.get_env("MORALIS_API_KEY")}]}
        ]
        |> Tesla.client()
      end
    end
    
    또한 Tesla.Env.clientapps/moralis에 상기 의존고를 미리 추가한다.
    diff --git a/apps/moralis/mix.exs b/apps/moralis/mix.exs
    index c2d36d6..18dba6b 100644
    --- a/apps/moralis/mix.exs
    +++ b/apps/moralis/mix.exs
    @@ -28,6 +28,8 @@ defmodule Moralis.Core.MixProject do
           # {:dep_from_hexpm, "~> 0.3.0"},
           # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
           # {:sibling_app_in_umbrella, in_umbrella: true}
    +      {:tesla, "~> 1.2"},
    +      {:poison, "~> 3.0"}
         ]
       end
     end
    

    API 클라이언트 실행


    환경 변수Moralis Admin에서 획득할 수 있는 API 키를 설정합니다.
    $ export MORALIS_API_KEY="********************"
    
    이후 iex로 시작하면 다음과 같이 실행됩니다.여기서 이더 eum에 있는 나의 주소mix.exs가 링크된 이더 eum의 메인 네트워크에 있는 NFFT의 일람을 얻었다.
    $ iex -S mix
    iex(1)> Moralis.client |> Moralis.Api.Account.get_nfts("0xC8cFA9Ab96B9e78961607d485c50135059C84840", chain: :eth, format: :decimal)
    
    21:45:43.419 [warning] Description: 'Authenticity is not established by certificate path validation'
         Reason: 'Option {verify, verify_peer} and cacertfile/cacerts is missing'
    
    {:ok,
     %Moralis.Model.NftOwnerCollection{
       cursor: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3aGVyZSI6eyJvd25lcl9vZiI6IjB4YzhjZmE5YWI5NmI5ZTc4OTYxNjA3ZDQ4NWM1MDEzNTA1OWM4NDg0MCJ9LCJsaW1pdCI6NTAwLCJvZmZzZXQiOjUwMCwib3JkZXIiOltbInRyYW5zZmVyX2luZGV4IiwiREVTQyJdXSwicGFnZSI6MSwia2V5IjoiMTQxNzcwMDguMzcuNTUuMCIsImlhdCI6MTY0NzQzNDc0M30.HfVwM7LtsZ6M1vY75lnHM7cLcrkKOmRlpDYrWyVCGTY",
       page: 0,
       page_size: 500,
       result: [
         %Moralis.Model.NftOwner{
           amount: "1",
           block_number: "14255778",
           block_number_minted: "14255778",
           contract_type: "ERC721",
           metadata: "{\"is_normalized\":true,\"name\":\"antipop.eth\",\"description\":\"antipop.eth, an ENS name.\",\"attributes\":[{\"trait_type\":\"Created Date\",\"display_type\":\"date\",\"value\":null},{\"trait_type\":\"Length\",\"display_type\":\"number\",\"value\":7},{\"trait_type\":\"Registration Date\",\"display_type\":\"date\",\"value\":1645531685000},{\"trait_type\":\"Expiration Date\",\"display_type\":\"date\",\"value\":1803316445000}],\"name_length\":7,\"url\":\"https://app.ens.domains/name/antipop.eth\",\"version\":0,\"background_image\":\"https://metadata.ens.domains/mainnet/avatar/antipop.eth\",\"image_url\":\"https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/0x681d2af5f952f2f3ced409b235e8b9ba1516133c8496c650394a5793562824e1/image\"}",
           name: "",
           owner_of: "0xc8cfa9ab96b9e78961607d485c50135059c84840",
           symbol: "",
           synced_at: "2022-02-24T20:10:07.244Z",
           token_address: "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85",
           token_id: "47092071322328660070279121323087064708284557536712030821400426890975974532321",
           token_uri: "https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/47092071322328660070279121323087064708284557536712030821400426890975974532321"
         },
         %Moralis.Model.NftOwner{
           amount: "1",
           block_number: "14246649",
           block_number_minted: "14246649",
           contract_type: "ERC721",
           metadata: "{\n  \"name\": \"Community Statement on \\\"NFT art\\\"\",\n  \"description\": \"This is an NFT indicating that you have signed the Community Statement on \\\"NFT art\\\".\\n\\nWebsite: [https://nft-art-statement.github.io](https://nft-art-statement.github.io)\\n\\nPDF: [https://ipfs.io/ipfs/Qmbuc7FMZ2qsUjSMtTG6FoD6sAigCzS9AyJUtQF2cMX4Qe](https://ipfs.io/ipfs/Qmbuc7FMZ2qsUjSMtTG6FoD6sAigCzS9AyJUtQF2cMX4Qe)\\n\\nOriginal image script: [https://openprocessing.org/sketch/1491110](https://openprocessing.org/sketch/1491110)\",\n  \"image\": \"https://ipfs.io/ipfs/QmURcYa9U1juYWTQaeNe2Cj9Xbxt6yXuZfx2G9XqhbGD7k\",\n  \"external_url\": \"https://nft-art-statement.github.io\"\n}",
           name: "Community Statement on NFT art",
           owner_of: "0xc8cfa9ab96b9e78961607d485c50135059c84840",
           symbol: "CSNA",
           synced_at: "2022-02-21T02:06:51.280Z",
           token_address: "0x01a45dfef5fc495f1b4c146319b79808be464dba",
           token_id: "1146429188785820425206666927417020747748991322176",
           token_uri: "https://ipfs.io/ipfs/QmXtwT89TTySmJYpvU9mNWi46Ro44x3pT5yh9m6yFN7Uy4"
         },
         %Moralis.Model.NftOwner{
           amount: "1",
           block_number: "14218466",
           block_number_minted: "14218466",
           contract_type: "ERC1155",
           metadata: "{\"name\":\"yellow tent\",\"description\":null,\"external_link\":null,\"image\":\"https://lh3.googleusercontent.com/AuRzNcrrInhkOIG6oqxALSM2Skn1OiOlp9tZyupx59zP-T50AaiNeydvsFLH2BhvIp0wJOtqWMJeJZ7QD4Xhlpl9HAHsJA6hfnX_2w\",\"animation_url\":null}",
           name: "OpenSea Shared Storefront",
           owner_of: "0xc8cfa9ab96b9e78961607d485c50135059c84840",
           symbol: "OPENSTORE",
           synced_at: "2022-02-16T17:28:39.722Z",
           token_address: "0x495f947276749ce646f68ac8c248420045cb7b5e",
           token_id: "73836485752685965752355914628923343070930493146497138125618145467993293848577",
           token_uri: "https://api.opensea.io/api/v1/metadata/0x495f947276749Ce646f68AC8c248420045cb7b5e/0xa33df84efd75309ae7ceb5aea44e1c7c02d98f4b000000000000010000000001"
         },
         %Moralis.Model.NftOwner{
           amount: "1",
           block_number: "14177008",
           block_number_minted: "14177008",
           contract_type: "ERC721",
           metadata: "{\"is_normalized\":true,\"name\":\"kentarokuribayashi.eth\",\"description\":\"kentarokuribayashi.eth, an ENS name.\",\"attributes\":[{\"trait_type\":\"Created Date\",\"display_type\":\"date\",\"value\":null},{\"trait_type\":\"Length\",\"display_type\":\"number\",\"value\":18},{\"trait_type\":\"Registration Date\",\"display_type\":\"date\",\"value\":1644478257000},{\"trait_type\":\"Expiration Date\",\"display_type\":\"date\",\"value\":1802263017000}],\"name_length\":18,\"url\":\"https://app.ens.domains/name/kentarokuribayashi.eth\",\"version\":0,\"background_image\":\"https://metadata.ens.domains/mainnet/avatar/kentarokuribayashi.eth\",\"image_url\":\"https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/0xcb266aacf362d37fef19506ce033febea8121aa26f8874a71703c26158a69b01/image\"}",
           name: "",
           owner_of: "0xc8cfa9ab96b9e78961607d485c50135059c84840",
           symbol: "",
           synced_at: "2022-02-23T15:28:07.577Z",
           token_address: "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85",
           token_id: "91887384698719783598076195110397135275850003826252571321438245958937884400385",
           token_uri: "https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/91887384698719783598076195110397135275850003826252571321438245958937884400385"
         }
       ],
       status: "SYNCED",
       total: 4
     }}
    

    끝말


    위에서 설명한 대로 API 클라이언트를 간단히 생성하고 원하는 결과를 API에서 얻을 수 있습니다.다음 단계는 GiitHub Actions 등을 통해 OpenAPI 모드에서 온 클라이언트를 자동화하는 것이지만 위와 같이 잘못될 수 있어 이번엔 그러지 못했다.만약 그 방면이 해결된다면, 나는 한번 해 보고 싶다.

    좋은 웹페이지 즐겨찾기