Merge pull request #1191 from gradha/pr_removes_abstypes
Moves abstypes content into manual.
This commit is contained in:
commit
2232c187a7
2 changed files with 65 additions and 152 deletions
152
doc/abstypes.txt
152
doc/abstypes.txt
|
|
@ -1,152 +0,0 @@
|
||||||
==============
|
|
||||||
Abstract types
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
Abstract types in Nimrod provide a means to model different `units`:idx: of
|
|
||||||
a `base type`:idx:.
|
|
||||||
|
|
||||||
|
|
||||||
Use case 1: SQL strings
|
|
||||||
-----------------------
|
|
||||||
An SQL statement that is passed from Nimrod to an SQL database might be
|
|
||||||
modelled as a string. However, using string templates and filling in the
|
|
||||||
values is vulnerable to the famous `SQL injection attack`:idx:\:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
proc query(db: TDbHandle, statement: TSQL) = ...
|
|
||||||
|
|
||||||
var
|
|
||||||
username: string
|
|
||||||
|
|
||||||
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
|
||||||
# Horrible security hole, but the compiler does not mind!
|
|
||||||
|
|
||||||
This can be avoided by distinguishing strings that contain SQL from strings
|
|
||||||
that don't. Abstract types provide a means to introduce a new string type
|
|
||||||
``TSQL`` that is incompatible with ``string``:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
type
|
|
||||||
TSQL = abstract string
|
|
||||||
|
|
||||||
proc query(db: TDbHandle, statement: TSQL) = ...
|
|
||||||
|
|
||||||
var
|
|
||||||
username: string
|
|
||||||
|
|
||||||
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
|
||||||
# Error at compile time: `query` expects an SQL string!
|
|
||||||
|
|
||||||
|
|
||||||
It is an essential property of abstract types that they **do not** imply a
|
|
||||||
subtype relation between the abtract type and its base type. Explict type
|
|
||||||
conversions from ``string`` to ``TSQL`` are allowed:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
proc properQuote(s: string): TSQL =
|
|
||||||
# quotes a string properly for an SQL statement
|
|
||||||
...
|
|
||||||
|
|
||||||
proc `%` (frmt: TSQL, values: openarray[string]): TSQL =
|
|
||||||
# quote each argument:
|
|
||||||
var v = values.each(properQuote)
|
|
||||||
# we need a temporary type for the type conversion :-(
|
|
||||||
type TStrSeq = seq[string]
|
|
||||||
# call strutils.`%`:
|
|
||||||
result = TSQL(string(frmt) % TStrSeq(v))
|
|
||||||
|
|
||||||
db.query("SELECT FROM users WHERE name = $1".TSQL % username)
|
|
||||||
|
|
||||||
Now we have compile-time checking against SQL injection attacks.
|
|
||||||
Since ``"".TSQL`` is transformed to ``TSQL("")`` no new syntax is needed
|
|
||||||
for nice looking ``TSQL`` string literals.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Use case 2: Money
|
|
||||||
-----------------
|
|
||||||
Different currencies should not be mixed in monetary calculations. Abstract
|
|
||||||
types are a perfect tool to model different currencies:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
type
|
|
||||||
TDollar = abstract int
|
|
||||||
TEuro = abstract int
|
|
||||||
|
|
||||||
var
|
|
||||||
d: TDollar
|
|
||||||
e: TEuro
|
|
||||||
|
|
||||||
echo d + 12
|
|
||||||
# Error: cannot add a number with no unit with a ``TDollar``
|
|
||||||
|
|
||||||
Unfortunetaly, ``d + 12.TDollar`` is not allowed either,
|
|
||||||
because ``+`` is defined for ``int`` (among others), not for ``TDollar``. So
|
|
||||||
we define our own ``+`` for dollars:
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
proc `+` (x, y: TDollar): TDollar =
|
|
||||||
result = TDollar(int(x) + int(y))
|
|
||||||
|
|
||||||
It does not make sense to multiply a dollar with a dollar, but with a
|
|
||||||
number without unit; and the same holds for division:
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
proc `*` (x: TDollar, y: int): TDollar =
|
|
||||||
result = TDollar(int(x) * y)
|
|
||||||
|
|
||||||
proc `*` (x: int, y: TDollar): TDollar =
|
|
||||||
result = TDollar(x * int(y))
|
|
||||||
|
|
||||||
proc `div` ...
|
|
||||||
|
|
||||||
This quickly gets tedious. The implementations are trivial and the compiler
|
|
||||||
should not generate all this code only to optimize it away later - after all
|
|
||||||
``+`` for dollars should produce the same binary code as ``+`` for ints.
|
|
||||||
The pragma ``borrow`` has been designed to solve this problem; in principle
|
|
||||||
it generates the trivial implementation for us:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
proc `*` (x: TDollar, y: int): TDollar {.borrow.}
|
|
||||||
proc `*` (x: int, y: TDollar): TDollar {.borrow.}
|
|
||||||
proc `div` (x: TDollar, y: int): TDollar {.borrow.}
|
|
||||||
|
|
||||||
The ``borrow`` pragma makes the compiler to use the same implementation as
|
|
||||||
the proc that deals with the abstract type's base type, so no code is
|
|
||||||
generated.
|
|
||||||
|
|
||||||
But it seems we still have to repeat all this boilerplate code for
|
|
||||||
the ``TEuro`` currency. Fortunately, Nimrod has a template mechanism:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
template Additive(typ: typeDesc): stmt =
|
|
||||||
proc `+` *(x, y: typ): typ {.borrow.}
|
|
||||||
proc `-` *(x, y: typ): typ {.borrow.}
|
|
||||||
|
|
||||||
# unary operators:
|
|
||||||
proc `+` *(x: typ): typ {.borrow.}
|
|
||||||
proc `-` *(x: typ): typ {.borrow.}
|
|
||||||
|
|
||||||
template Multiplicative(typ, base: typeDesc): stmt =
|
|
||||||
proc `*` *(x: typ, y: base): typ {.borrow.}
|
|
||||||
proc `*` *(x: base, y: typ): typ {.borrow.}
|
|
||||||
proc `div` *(x: typ, y: base): typ {.borrow.}
|
|
||||||
proc `mod` *(x: typ, y: base): typ {.borrow.}
|
|
||||||
|
|
||||||
template Comparable(typ: typeDesc): stmt =
|
|
||||||
proc `<` * (x, y: typ): bool {.borrow.}
|
|
||||||
proc `<=` * (x, y: typ): bool {.borrow.}
|
|
||||||
proc `==` * (x, y: typ): bool {.borrow.}
|
|
||||||
|
|
||||||
template DefineCurrency(typ, base: expr): stmt =
|
|
||||||
type
|
|
||||||
typ* = abstract base
|
|
||||||
Additive(typ)
|
|
||||||
Multiplicative(typ, base)
|
|
||||||
Comparable(typ)
|
|
||||||
|
|
||||||
DefineCurrency(TDollar, int)
|
|
||||||
DefineCurrency(TEuro, int)
|
|
||||||
|
|
||||||
|
|
@ -1550,6 +1550,10 @@ of a distinct type that it **does not** imply a subtype relation between it
|
||||||
and its base type. Explicit type conversions from a distinct type to its
|
and its base type. Explicit type conversions from a distinct type to its
|
||||||
base type and vice versa are allowed.
|
base type and vice versa are allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Modelling currencies
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
A distinct type can be used to model different physical `units`:idx: with a
|
A distinct type can be used to model different physical `units`:idx: with a
|
||||||
numerical base type, for example. The following example models currencies.
|
numerical base type, for example. The following example models currencies.
|
||||||
|
|
||||||
|
|
@ -1657,6 +1661,67 @@ certain builtin operations to be lifted:
|
||||||
Currently only the dot accessor can be borrowed in this way.
|
Currently only the dot accessor can be borrowed in this way.
|
||||||
|
|
||||||
|
|
||||||
|
Avoiding SQL injection attacks
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An SQL statement that is passed from Nimrod to an SQL database might be
|
||||||
|
modelled as a string. However, using string templates and filling in the
|
||||||
|
values is vulnerable to the famous `SQL injection attack`:idx:\:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
import strutils
|
||||||
|
|
||||||
|
proc query(db: TDbHandle, statement: string) = ...
|
||||||
|
|
||||||
|
var
|
||||||
|
username: string
|
||||||
|
|
||||||
|
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
||||||
|
# Horrible security hole, but the compiler does not mind!
|
||||||
|
|
||||||
|
This can be avoided by distinguishing strings that contain SQL from strings
|
||||||
|
that don't. Distinct types provide a means to introduce a new string type
|
||||||
|
``TSQL`` that is incompatible with ``string``:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
type
|
||||||
|
TSQL = distinct string
|
||||||
|
|
||||||
|
proc query(db: TDbHandle, statement: TSQL) = ...
|
||||||
|
|
||||||
|
var
|
||||||
|
username: string
|
||||||
|
|
||||||
|
db.query("SELECT FROM users WHERE name = '$1'" % username)
|
||||||
|
# Error at compile time: `query` expects an SQL string!
|
||||||
|
|
||||||
|
|
||||||
|
It is an essential property of abstract types that they **do not** imply a
|
||||||
|
subtype relation between the abtract type and its base type. Explict type
|
||||||
|
conversions from ``string`` to ``TSQL`` are allowed:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
import strutils, sequtils
|
||||||
|
|
||||||
|
proc properQuote(s: string): TSQL =
|
||||||
|
# quotes a string properly for an SQL statement
|
||||||
|
return TSQL(s)
|
||||||
|
|
||||||
|
proc `%` (frmt: TSQL, values: openarray[string]): TSQL =
|
||||||
|
# quote each argument:
|
||||||
|
let v = values.mapIt(TSQL, properQuote(it))
|
||||||
|
# we need a temporary type for the type conversion :-(
|
||||||
|
type TStrSeq = seq[string]
|
||||||
|
# call strutils.`%`:
|
||||||
|
result = TSQL(string(frmt) % TStrSeq(v))
|
||||||
|
|
||||||
|
db.query("SELECT FROM users WHERE name = '$1'".TSQL % [username])
|
||||||
|
|
||||||
|
Now we have compile-time checking against SQL injection attacks. Since
|
||||||
|
``"".TSQL`` is transformed to ``TSQL("")`` no new syntax is needed for nice
|
||||||
|
looking ``TSQL`` string literals. The hypothetical ``TSQL`` type actually
|
||||||
|
exists in the library as the `TSqlQuery type <db_sqlite.html#TSqlQuery>`_ of
|
||||||
|
modules like `db_sqlite <db_sqlite.html>`_.
|
||||||
|
|
||||||
|
|
||||||
Void type
|
Void type
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue