-module(glupbit@websocket@subscription). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glupbit/websocket/subscription.gleam"). -export([build_list_subscriptions_message/1, build_subscription_message/3, ticker_data_decoder/0, trade_data_decoder/0, orderbook_data_decoder/0, subscription_info_decoder/0, list_subscriptions_response_decoder/0]). -export_type([ws_format/0, subscription/0, ticker_data/0, trade_data/0, orderbook_data/0, subscription_info/0, list_subscriptions_response/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" WebSocket subscription types, message builders, and response decoders.\n"). -type ws_format() :: default | simple | json_list | simple_list. -type subscription() :: {ticker_sub, list(glupbit@types:market()), boolean(), boolean()} | {trade_sub, list(glupbit@types:market()), boolean(), boolean()} | {orderbook_sub, list(glupbit@types:market()), gleam@option:option(binary()), boolean(), boolean()} | {candle_sub, list(glupbit@types:market()), binary(), boolean(), boolean()} | {my_order_sub, gleam@option:option(list(glupbit@types:market()))} | my_asset_sub. -type ticker_data() :: {ticker_data, binary(), float(), binary(), float(), float(), integer()}. -type trade_data() :: {trade_data, binary(), float(), float(), binary(), integer(), integer()}. -type orderbook_data() :: {orderbook_data, binary(), float(), float(), list(glupbit@quotation@orderbook:orderbook_unit()), integer()}. -type subscription_info() :: {subscription_info, binary(), gleam@option:option(list(binary())), gleam@option:option(float())}. -type list_subscriptions_response() :: {list_subscriptions_response, binary(), list(subscription_info()), binary()}. -file("src/glupbit/websocket/subscription.gleam", 64). ?DOC(" Build a LIST_SUBSCRIPTIONS request message as a JSON string.\n"). -spec build_list_subscriptions_message(binary()) -> binary(). build_list_subscriptions_message(Ticket) -> Ticket_obj = gleam@json:object( [{<<"ticket"/utf8>>, gleam@json:string(Ticket)}] ), Method_obj = gleam@json:object( [{<<"method"/utf8>>, gleam@json:string(<<"LIST_SUBSCRIPTIONS"/utf8>>)}] ), gleam@json:to_string( gleam@json:preprocessed_array([Ticket_obj, Method_obj]) ). -file("src/glupbit/websocket/subscription.gleam", 70). -spec ws_format_to_string(ws_format()) -> binary(). ws_format_to_string(Format) -> case Format of default -> <<"DEFAULT"/utf8>>; simple -> <<"SIMPLE"/utf8>>; json_list -> <<"JSON_LIST"/utf8>>; simple_list -> <<"SIMPLE_LIST"/utf8>> end. -file("src/glupbit/websocket/subscription.gleam", 135). -spec build_sub_json( binary(), list(glupbit@types:market()), gleam@option:option(binary()), gleam@option:option(binary()), boolean(), boolean() ) -> gleam@json:json(). build_sub_json(Type_name, Codes, Level, _, Is_only_snapshot, Is_only_realtime) -> Fields = [{<<"type"/utf8>>, gleam@json:string(Type_name)}, {<<"codes"/utf8>>, gleam@json:array( Codes, fun(M) -> gleam@json:string(glupbit@types:market_to_string(M)) end )}], Fields@1 = case Level of {some, L} -> [{<<"level"/utf8>>, gleam@json:string(L)} | Fields]; none -> Fields end, Fields@2 = case Is_only_snapshot of true -> [{<<"isOnlySnapshot"/utf8>>, gleam@json:bool(true)} | Fields@1]; false -> Fields@1 end, Fields@3 = case Is_only_realtime of true -> [{<<"isOnlyRealtime"/utf8>>, gleam@json:bool(true)} | Fields@2]; false -> Fields@2 end, gleam@json:object(Fields@3). -file("src/glupbit/websocket/subscription.gleam", 79). -spec subscription_to_json(subscription()) -> gleam@json:json(). subscription_to_json(Sub) -> case Sub of {ticker_sub, Codes, Is_only_snapshot, Is_only_realtime} -> build_sub_json( <<"ticker"/utf8>>, Codes, none, none, Is_only_snapshot, Is_only_realtime ); {trade_sub, Codes@1, Is_only_snapshot@1, Is_only_realtime@1} -> build_sub_json( <<"trade"/utf8>>, Codes@1, none, none, Is_only_snapshot@1, Is_only_realtime@1 ); {orderbook_sub, Codes@2, Level, Is_only_snapshot@2, Is_only_realtime@2} -> build_sub_json( <<"orderbook"/utf8>>, Codes@2, Level, none, Is_only_snapshot@2, Is_only_realtime@2 ); {candle_sub, Codes@3, Unit, Is_only_snapshot@3, Is_only_realtime@3} -> build_sub_json( <<"candle."/utf8, Unit/binary>>, Codes@3, none, none, Is_only_snapshot@3, Is_only_realtime@3 ); {my_order_sub, Codes@4} -> Base = [{<<"type"/utf8>>, gleam@json:string(<<"myOrder"/utf8>>)}], Fields = case Codes@4 of {some, C} -> [{<<"codes"/utf8>>, gleam@json:array( C, fun(M) -> gleam@json:string( glupbit@types:market_to_string(M) ) end )} | Base]; none -> Base end, gleam@json:object(Fields); my_asset_sub -> gleam@json:object( [{<<"type"/utf8>>, gleam@json:string(<<"myAsset"/utf8>>)}] ) end. -file("src/glupbit/websocket/subscription.gleam", 50). ?DOC( " Build a WebSocket subscription message as a JSON string.\n" " Returns a JSON array: [{\"ticket\":\"...\"}, {\"type\":\"ticker\",\"codes\":[...]}, ..., {\"format\":\"DEFAULT\"}]\n" ). -spec build_subscription_message(binary(), list(subscription()), ws_format()) -> binary(). build_subscription_message(Ticket, Subscriptions, Format) -> Ticket_obj = gleam@json:object( [{<<"ticket"/utf8>>, gleam@json:string(Ticket)}] ), Sub_objs = gleam@list:map(Subscriptions, fun subscription_to_json/1), Format_obj = gleam@json:object( [{<<"format"/utf8>>, gleam@json:string(ws_format_to_string(Format))}] ), All = [Ticket_obj | lists:append(Sub_objs, [Format_obj])], gleam@json:to_string(gleam@json:preprocessed_array(All)). -file("src/glupbit/websocket/subscription.gleam", 205). ?DOC(" Decoder for WebSocket ticker data.\n"). -spec ticker_data_decoder() -> gleam@dynamic@decode:decoder(ticker_data()). ticker_data_decoder() -> gleam@dynamic@decode:field( <<"code"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Code) -> gleam@dynamic@decode:field( <<"trade_price"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_float/1}, fun(Trade_price) -> gleam@dynamic@decode:field( <<"change"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Change) -> gleam@dynamic@decode:field( <<"signed_change_rate"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_float/1}, fun(Signed_change_rate) -> gleam@dynamic@decode:field( <<"acc_trade_volume_24h"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_float/1}, fun(Acc_trade_volume_24h) -> gleam@dynamic@decode:field( <<"timestamp"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_int/1}, fun(Timestamp) -> gleam@dynamic@decode:success( {ticker_data, Code, Trade_price, Change, Signed_change_rate, Acc_trade_volume_24h, Timestamp} ) end ) end ) end ) end ) end ) end ). -file("src/glupbit/websocket/subscription.gleam", 223). ?DOC(" Decoder for WebSocket trade data.\n"). -spec trade_data_decoder() -> gleam@dynamic@decode:decoder(trade_data()). trade_data_decoder() -> gleam@dynamic@decode:field( <<"code"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Code) -> gleam@dynamic@decode:field( <<"trade_price"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_float/1}, fun(Trade_price) -> gleam@dynamic@decode:field( <<"trade_volume"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_float/1}, fun(Trade_volume) -> gleam@dynamic@decode:field( <<"ask_bid"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Ask_bid) -> gleam@dynamic@decode:field( <<"trade_timestamp"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_int/1}, fun(Trade_timestamp) -> gleam@dynamic@decode:field( <<"sequential_id"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_int/1}, fun(Sequential_id) -> gleam@dynamic@decode:success( {trade_data, Code, Trade_price, Trade_volume, Ask_bid, Trade_timestamp, Sequential_id} ) end ) end ) end ) end ) end ) end ). -file("src/glupbit/websocket/subscription.gleam", 241). ?DOC(" Decode a JSON number as Float, accepting both float and int values.\n"). -spec ws_number() -> gleam@dynamic@decode:decoder(float()). ws_number() -> gleam@dynamic@decode:one_of( {decoder, fun gleam@dynamic@decode:decode_float/1}, [begin _pipe = {decoder, fun gleam@dynamic@decode:decode_int/1}, gleam@dynamic@decode:map(_pipe, fun erlang:float/1) end] ). -file("src/glupbit/websocket/subscription.gleam", 245). -spec ws_orderbook_unit_decoder() -> gleam@dynamic@decode:decoder(glupbit@quotation@orderbook:orderbook_unit()). ws_orderbook_unit_decoder() -> gleam@dynamic@decode:field( <<"ask_price"/utf8>>, ws_number(), fun(Ask_price) -> gleam@dynamic@decode:field( <<"bid_price"/utf8>>, ws_number(), fun(Bid_price) -> gleam@dynamic@decode:field( <<"ask_size"/utf8>>, ws_number(), fun(Ask_size) -> gleam@dynamic@decode:field( <<"bid_size"/utf8>>, ws_number(), fun(Bid_size) -> gleam@dynamic@decode:success( {orderbook_unit, Ask_price, Bid_price, Ask_size, Bid_size} ) end ) end ) end ) end ). -file("src/glupbit/websocket/subscription.gleam", 259). ?DOC(" Decoder for WebSocket orderbook data.\n"). -spec orderbook_data_decoder() -> gleam@dynamic@decode:decoder(orderbook_data()). orderbook_data_decoder() -> gleam@dynamic@decode:field( <<"code"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Code) -> gleam@dynamic@decode:field( <<"total_ask_size"/utf8>>, ws_number(), fun(Total_ask_size) -> gleam@dynamic@decode:field( <<"total_bid_size"/utf8>>, ws_number(), fun(Total_bid_size) -> gleam@dynamic@decode:field( <<"orderbook_units"/utf8>>, gleam@dynamic@decode:list( ws_orderbook_unit_decoder() ), fun(Orderbook_units) -> gleam@dynamic@decode:field( <<"timestamp"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_int/1}, fun(Timestamp) -> gleam@dynamic@decode:success( {orderbook_data, Code, Total_ask_size, Total_bid_size, Orderbook_units, Timestamp} ) end ) end ) end ) end ) end ). -file("src/glupbit/websocket/subscription.gleam", 298). ?DOC(" Decoder for a single subscription info item.\n"). -spec subscription_info_decoder() -> gleam@dynamic@decode:decoder(subscription_info()). subscription_info_decoder() -> gleam@dynamic@decode:field( <<"type"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Type_name) -> gleam@dynamic@decode:optional_field( <<"codes"/utf8>>, none, gleam@dynamic@decode:optional( gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_string/1} ) ), fun(Codes) -> gleam@dynamic@decode:optional_field( <<"level"/utf8>>, none, gleam@dynamic@decode:optional( {decoder, fun gleam@dynamic@decode:decode_float/1} ), fun(Level) -> gleam@dynamic@decode:success( {subscription_info, Type_name, Codes, Level} ) end ) end ) end ). -file("src/glupbit/websocket/subscription.gleam", 314). ?DOC(" Decoder for the LIST_SUBSCRIPTIONS response message.\n"). -spec list_subscriptions_response_decoder() -> gleam@dynamic@decode:decoder(list_subscriptions_response()). list_subscriptions_response_decoder() -> gleam@dynamic@decode:field( <<"method"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Method) -> gleam@dynamic@decode:field( <<"result"/utf8>>, gleam@dynamic@decode:list(subscription_info_decoder()), fun(Result) -> gleam@dynamic@decode:field( <<"ticket"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Ticket) -> gleam@dynamic@decode:success( {list_subscriptions_response, Method, Result, Ticket} ) end ) end ) end ).