1

I am modelling a REST API using RAML. The response body of an endpoint (JSON format) is a financial transactions list. Each transactions contains an amount of money: currency and numeric value. The following is the snippet of my RAML file, please note the property amount in the Transaction type:

  # %RAML 1.0
  title: Trading details API
  version: v1
  mediaType: application/json
  baseUri: http://my.host.io/api/trading/v1/
  types:
    Transactions:
      type: Transaction[]
      minItems: 1
    Transaction:
      type: object
      properties:
        refNum:
          type: string 
        amount:
          type: ????
        currency:
          type: string
          minLength: 2
          maxLength: 3

  /trades
    get:
      description: Get details for a given trade
      queryParameters:
        userId:
          type: integer
          required: true

      responses:
        200:
          body:
            application/json:
              type: Transactions

Unfortunately RAML has no Built-in decimal type, and the other numeric types (integer, float or double) are not suitable for this scope, mainly because I need to specify the number of digits after the . .

So question is: in RAML how do I correctly model the type amount?

I need to provide an exact definition of the type for each response body values, because this file will be the contract between the backend and frontend (developed by 2 different teams).

Any helps is welcomed.

Please note that I made some research on SO, and the closest question to mine is: How to define money amounts in an API . But it is not related to RAML modelling, and the answers are not helping to me.

Davide Martorana
  • 691
  • 3
  • 8
  • 20

2 Answers2

1

RAML has a similar construct to the one in JSON Schema. You'll want to use type: number in combination with multipleOf to describe decimal precision.

#%RAML 1.0 DataType

type: number
multipleOf: 0.01
  • Hi Jonathan, I was thinking about adopting your solution, that in general tackle the problem we are trying to solve in this question. Unfortunately, by using `multipleOf` you cannot enforce to include decimal positions. For instance, **`25.00`** and **`25`** are both accepted. If it is acceptable with the model you are designing, your solution is **great**. On the contrary, if you want to enforce to accept only the first one (`25.00`), the _facet_ `multipleOf` does not help. I was after this second scenario. Thanks for your contribution. – Davide Martorana Apr 06 '18 at 06:59
  • Not to dismiss your concern but wouldn't most programming languages cast `25` to `25.00`? why not have the API allow it then? – Jonathan Stoikovitch Apr 16 '18 at 17:06
  • In my point of view a good API designing solution define the body messages to be consistent across the entire API, regardless the programming language representation that the specific micro-service is using. Moreover, if you use Java you don't want to represent amount (decimal) with double due to the well known bugs of JVM. This is the reason why they introduced `BigDecimal`, which recommended constructor is `new BigDecimal(String)`. However a good design must be free from languages constraint. Thanks for your appreciated contribution. – Davide Martorana Feb 23 '19 at 00:18
0

After months I come back to share my experience.

The way I worked around it was by using the type string and a pattern. I am aware of the many concerns around changing the data type from number to string, but this approach is elegant, robust, flexible and still simple to test and understand.

The API consumers are forced to format the amount in the correct way and the messages coming in and out of the API are consistent, consistency cannot be guaranteed by using multiplyOf 0.0001 (where 25 and 25.0000 are both accepted).

I reused over and over this solution with great results. Therefore I am sharing this with the community.

Solution:

   [...]
   amount:
     type: string
     pattern: "^(([1-9][0-9]*)|[0])[.]([0-9]{4})$"
     currency:
       type: string
          ...

The pattern accepts 4 digits on the decimal part, forces to use a . and the amount cannot starts with 0, with the exception of 0.xxxx family of numbers.

The following is an examples list of accepted numbers:

1.0000
54.0000
23456.1234
1.9800
0.0000
0.0001

Instead the following is an example list of rejected:

0123.3453
12.12
1.1
000
01.0000
1.0
1.00
4.000

Moreover, you can specify the max number of digits on the left side (in this example 10):

pattern: "^(([1-9][0-9]{0,9})|[0])[.]([0-9]{4})$"

Example of accepted numbers:

1234567890.1234
3.5555
0.1234

Example of rejected numbers:

12345678901.1234
123456789012.1234
Davide Martorana
  • 691
  • 3
  • 8
  • 20
  • I'm not sure this pattern is correct: pattern: "^(([1-9][0-9]*)|[0])[.]([0-9]{4})$" This shouldn't accept 0.xxxx numbers because the first block isn't optional. This should fix it: pattern: "^(([1-9]?[0-9]+)|[0])[.]([0-9]{4})$" but then it won't restrict 0xx.xxxx numbers.. I think you'd have to use like a look ahead or look behind to fully disallow leading 0s except when followed by a dot. – Geoff Potter Dec 08 '21 at 16:35
  • Thanks for your comment @GeoffPotter Actually your patter allows 000[....].XXXX Which is not what I meant to have. The first part of the pattern is ^((__)**|[0]**)[.]. Emphasis is on "...|[0]" which means: either a digit starting with a number [1-9] *OR* a _single_ digit of Zero. Which was the initial aim of the pattern – Davide Martorana Dec 16 '21 at 15:14