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

Add CQG CMS API protobuf Haskell package #1

Merged
merged 7 commits into from
Jun 4, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---

name: Test

on:
pull_request:
types:
- synchronize
- opened
- reopened
- ready_for_review

jobs:
test:
if: github.event.pull_request.draft == false

runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

# Github actions L1 cache
- name: stack home cache
id: cache-home-stack
uses: actions/cache@v2
with:
path: |
/github/home/.stack
key: ${{ runner.os }}-home-stack-${{ hashFiles('stack.yaml.lock') }}
restore-keys: |
${{ runner.os }}-home-stack-

- name: stack work cache
uses: actions/cache@v2
id: cache-stack-work
with:
path: |
**/.stack-work
key: ${{ runner.os }}-stack-work-${{ hashFiles('stack.yaml.lock') }}-${{ hashFiles('aws-ec2-knownhosts.cabal') }}
restore-keys: |
${{ runner.os }}-stack-work-${{ hashFiles('stack.yaml.lock') }}-
${{ runner.os }}-stack-work-

# Build
- name: stack build
run: |
stack build --test --bench --no-run-tests --no-run-benchmarks --ghc-options="+RTS -A256m -I0 -RTS" --no-interleaved-output

# Test
- name: stack test
run: |
stack test --ghc-options="+RTS -A256m -I0 -RTS" --no-interleaved-output
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
dist
dist-*
cabal-dev
*.o
*.hi
*.hie
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# cqg-api-client

This repo contains Haskell packages for working with CQG's APIs:

- [CQG CMS API](https://partners.cqg.com/api-resources/cms-api)
- [CQG Web API](https://partners.cqg.com/api-resources/web-api) (_not yet implemented_)
11 changes: 11 additions & 0 deletions cqg-cms-api-proto/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog for `cqg-cms-api-proto`

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to the
[Haskell Package Versioning Policy](https://pvp.haskell.org/).

## Unreleased

## 0.1.0.0 - YYYY-MM-DD
1 change: 1 addition & 0 deletions cqg-cms-api-proto/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions cqg-cms-api-proto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# cqg-cms-api-proto
2 changes: 2 additions & 0 deletions cqg-cms-api-proto/Setup.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain
68 changes: 68 additions & 0 deletions cqg-cms-api-proto/cqg-cms-api-proto.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.36.0.
--
-- see: https://github.com/sol/hpack

name: cqg-cms-api-proto
version: 0.1.0.0
synopsis: Haskell modules generated from Protobuf definitions of the CQG CMS API
description: Please see the README on GitHub at <https://github.com/bitnomial/cqg-api-client/cqg-cms-api-proto#readme>
category: Web
author: Bitnomial
maintainer: Bitnomial <[email protected]>
copyright: 2024 Bitnomial, Inc
license: AllRightsReserved
license-file: LICENSE
build-type: Simple
extra-source-files:
README.md
CHANGELOG.md
proto/CMS/api_limit_1.proto
proto/CMS/cmsapi_1.proto
proto/CMS/common_1.proto
proto/CMS/location_1.proto
proto/CMS/log_event_1.proto
proto/CMS/login_1.proto
proto/CMS/metadata_1.proto
proto/CMS/order_1.proto
proto/CMS/session_context_1.proto
proto/CMS/traderouting_1.proto
proto/common/decimal.proto
proto/common/shared_1.proto

library
exposed-modules:
Proto.CMS.ApiLimit1
Proto.CMS.ApiLimit1_Fields
Proto.CMS.Cmsapi1
Proto.CMS.Cmsapi1_Fields
Proto.CMS.Common1
Proto.CMS.Common1_Fields
Proto.CMS.Location1
Proto.CMS.Location1_Fields
Comment on lines +34 to +43
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having these Haskell modules all be named Proto.* feels somewhat unfortunate. It feels like it would be better if we could name them something like CQG.Proto.CMS.ApiLimit1, but it doesn't appear that the proto-lens library has any way of generating modules with a different naming scheme.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it would pretty straightforward to rename the modules as part of running generate.sh. I agree that we should put them into the namespace that we want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, how do you feel about formatting the code as part of generation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it would pretty straightforward to rename the modules as part of running generate.sh

I imagine we could do this with some mvs and seds, but it would be something we might have to fiddle with again in the future when upgrading the proto-lens version. I'm also not sure how big of an advantage it would be.

I'm imagining we have this cqg-cms-api-proto library, which just contains the generated Haskell modules, like in this PR.

And then we have a separate library like cqg-cms-api-client, which contains the higher-level functions, intended for use by end-users (which in this case, would be our FCM libraries in the bitnomial repo).

Right now, my plan is to set up the cqg-cms-api-client package so it is fully-usable as-is, and re-exports everything necessary from the cqg-cms-api-proto library. So end-users wouldn't have to touch the cqg-cms-api-proto library at all, it would just be an internal implementation detail.

As long as end-users don't have to directly use cqg-cms-api-proto, I imagine it would be fine to leave the module names as Proto.CMS.* and Proto.Common.*.

how do you feel about formatting the code as part of generation?

I think this would be relatively straightforward to add, but:

  1. The generated code is so verbose that it is pretty hard to read. I don't think that a different formatting style would make it much easier.
  2. In my experience, proto-lens follows a pretty good pattern, so the generated protobuf Haskell modules are pretty easy to use once you get the hang of proto-lens. In practice it feels easier to look at the haddocks or the underlying .proto files (as opposed to the source of the generated Haskell files).
  3. It appears that the generated code at least has a consistent formatting (even if it is not the formatting style we normally use).

Proto.CMS.LogEvent1
Proto.CMS.LogEvent1_Fields
Proto.CMS.Login1
Proto.CMS.Login1_Fields
Proto.CMS.Metadata1
Proto.CMS.Metadata1_Fields
Proto.CMS.Order1
Proto.CMS.Order1_Fields
Proto.CMS.SessionContext1
Proto.CMS.SessionContext1_Fields
Proto.CMS.Traderouting1
Proto.CMS.Traderouting1_Fields
Proto.Common.Decimal
Proto.Common.Decimal_Fields
Proto.Common.Shared1
Proto.Common.Shared1_Fields
other-modules:
Paths_cqg_cms_api_proto
hs-source-dirs:
src
ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wno-unused-do-bind -Wunused-packages -funbox-strict-fields -fwrite-ide-info
build-depends:
base >=4.7 && <5
, proto-lens-runtime
default-language: Haskell2010
25 changes: 25 additions & 0 deletions cqg-cms-api-proto/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash
# nixos-24.05 as of 2024-06-01
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/805a384895c696f802a9bf5bf4720f37385df547.tar.gz
#! nix-shell -p curl
#! nix-shell -p unzip
#! nix-shell -p protobuf
#! nix-shell -p haskellPackages.proto-lens-protoc
#
# This script will download and regenerate the CQG CMS API Protobuf files.

# Make sure shellcheck understands that this is a Bash script.
# shellcheck shell=bash

set -eumo pipefail

shopt -s globstar

rm -rf proto/*
curl 'https://partners.cqg.com/cms/protocol/production/CMS.zip' > proto/CMS.zip
unzip proto/CMS.zip -d proto/
rm proto/CMS.zip

rm -rf src/*
protoc --plugin=protoc-gen-haskell="$(which proto-lens-protoc)" --haskell_out=src/ --proto_path=proto proto/**/*.proto
39 changes: 39 additions & 0 deletions cqg-cms-api-proto/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: "cqg-cms-api-proto"
version: "0.1.0.0"
maintainer: "Bitnomial <[email protected]>"
license: "AllRightsReserved"
author: "Bitnomial"
copyright: "2024 Bitnomial, Inc"

extra-source-files:
- README.md
- CHANGELOG.md
- proto/**/*.proto

synopsis: "Haskell modules generated from Protobuf definitions of the CQG CMS API"
category: "Web"

# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: "Please see the README on GitHub at <https://github.com/bitnomial/cqg-api-client/cqg-cms-api-proto#readme>"

dependencies:
- base >= 4.7 && < 5
- proto-lens-runtime

ghc-options:
- -Wall
- -Wcompat
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
- -Wno-unused-do-bind
- -Wunused-packages
- -funbox-strict-fields
- -fwrite-ide-info

library:
source-dirs: src
85 changes: 85 additions & 0 deletions cqg-cms-api-proto/proto/CMS/api_limit_1.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// CQG Customer Management System API user limit protocol

syntax = "proto2";

package api_limit_1;

import "common/shared_1.proto";

// Type of limit.
enum ApiLimitType
{
// Limit type unspecifed and must be ignored.
API_LIMIT_TYPE_UNSPECIFIED = 0;

// Maximum number of messages per period.
// Messages exceeding the limit are rejected.
API_LIMIT_TYPE_CLIENT_MESSAGES_RATE = 1;
}

// Request for API limit.
message ApiLimitRequestEntry
{
// [required] Limit type.
// This field is associated with ApiLimitType enum type.
optional uint32 limit_type = 1;

// Name of entity to request limit for. Set of possible values depends on limit_type:
// - API_LIMIT_CLIENT_MESSAGES_RATE: Full name of a client message (e.g. information_request.user_info_request).
//
// If not set, then all configured limits of requested type will be returned.
optional string entity_name = 2;
}

// Request for API limits.
message ApiLimitRequest
{
// Limits to request.
// If empty, all configured types of limits will be returned.
repeated ApiLimitRequestEntry limits = 1;
}

// Status of limit entry in result message.
enum ApiLimitEntryStatusCode
{
// Entry not specified and must be ignored.
API_LIMIT_ENTRY_STATUS_CODE_UNSPECIFIED = 0;

// Value is populated.
API_LIMIT_ENTRY_STATUS_CODE_SUCCESS = 1;

// Limit type was not found.
API_LIMIT_ENTRY_STATUS_CODE_NOT_FOUND = 2;
}

// Information about a limit set for current session of CMS API user.
message ApiLimitEntry
{
// Limit type.
// This field is associated with ApiLimitType enum type.
optional uint32 limit_type = 1;

// Name of entity this limit relates to. For more details see entity_name in ApiLimitRequestEntry.
// If not set, then this will be a default limit for all entities of the same limit type.
optional string entity_name = 2;

// Result status of the requested limit.
// This field is associated with ApiLimitEntryStatusCode enum type.
optional uint32 status_code = 3;

// Limit value.
optional uint32 value = 4;

// Populated if the limit is a rate = value / period_sec.
optional uint32 period_sec = 5;

// Limit description.
optional shared_1.Text description = 6;
}

// Result of ApiLimitRequest.
message ApiLimitResult
{
// Output entries.
repeated ApiLimitEntry limit_entries = 1;
}
Loading
Loading