Packages

A Gleam library for validating and generating GTIN (Global Trade Item Number) codes according to GS1 specification.

Current section

Files

Jump to
gl_gtin src gl_gtin@validation.erl
Raw

src/gl_gtin@validation.erl

-module(gl_gtin@validation).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gl_gtin/validation.gleam").
-export([validate/1, normalize/1]).
-export_type([gtin_format/0, validation_error/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(
" Validation module for GTIN codes.\n"
"\n"
" Implements core validation logic for GTIN codes including format detection,\n"
" check digit verification, and GTIN normalization.\n"
).
-type gtin_format() :: gtin8 | gtin12 | gtin13 | gtin14.
-type validation_error() :: {invalid_length, integer()} |
invalid_check_digit |
invalid_characters |
invalid_format.
-file("src/gl_gtin/validation.gleam", 53).
?DOC(
" Determine the GTIN format from a digit count.\n"
"\n"
" Maps digit counts to their corresponding GTIN format types.\n"
" Valid lengths are 8, 12, 13, and 14 digits.\n"
"\n"
" # Arguments\n"
"\n"
" * `length` - Number of digits\n"
"\n"
" # Returns\n"
"\n"
" Ok(GtinFormat) if the length is valid (8, 12, 13, or 14), Error otherwise.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" validate_length(8)\n"
" // -> Ok(Gtin8)\n"
"\n"
" validate_length(13)\n"
" // -> Ok(Gtin13)\n"
"\n"
" validate_length(10)\n"
" // -> Error(InvalidLength(got: 10))\n"
" ```\n"
).
-spec validate_length(integer()) -> {ok, gtin_format()} |
{error, validation_error()}.
validate_length(Length) ->
case Length of
8 ->
{ok, gtin8};
12 ->
{ok, gtin12};
13 ->
{ok, gtin13};
14 ->
{ok, gtin14};
_ ->
{error, {invalid_length, Length}}
end.
-file("src/gl_gtin/validation.gleam", 86).
?DOC(
" Parse a string to a list of digits.\n"
"\n"
" Converts each character in the string to an integer digit.\n"
" Returns an error if any character is not a digit.\n"
" This is used internally during validation to convert the input string.\n"
"\n"
" # Arguments\n"
"\n"
" * `code` - String to parse\n"
"\n"
" # Returns\n"
"\n"
" Ok(digit_list) if all characters are digits, Error(InvalidCharacters) otherwise.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" parse_digits(\"12345\")\n"
" // -> Ok([1, 2, 3, 4, 5])\n"
"\n"
" parse_digits(\"123a5\")\n"
" // -> Error(InvalidCharacters)\n"
" ```\n"
).
-spec parse_digits(binary()) -> {ok, list(integer())} |
{error, validation_error()}.
parse_digits(Code) ->
Chars = gleam@string:split(Code, <<""/utf8>>),
Parsed = gleam@list:try_map(
Chars,
fun(Char) -> case gl_gtin@internal@utils:parse_digit(Char) of
{ok, Digit} ->
{ok, Digit};
{error, _} ->
{error, invalid_characters}
end end
),
Parsed.
-file("src/gl_gtin/validation.gleam", 121).
?DOC(
" Validate the check digit of a GTIN.\n"
"\n"
" Extracts all digits except the last one, calculates what the check digit\n"
" should be using the GS1 Modulo 10 algorithm, and compares it to the actual check digit.\n"
" This is a critical validation step that ensures data integrity.\n"
"\n"
" # Arguments\n"
"\n"
" * `digits` - List of all digits including check digit\n"
"\n"
" # Returns\n"
"\n"
" Ok(Nil) if check digit is valid, Error(InvalidCheckDigit) otherwise.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" validate_check_digit([6, 2, 9, 1, 0, 4, 1, 5, 0, 0, 2, 1, 3])\n"
" // -> Ok(Nil)\n"
"\n"
" validate_check_digit([6, 2, 9, 1, 0, 4, 1, 5, 0, 0, 2, 1, 4])\n"
" // -> Error(InvalidCheckDigit)\n"
" ```\n"
).
-spec validate_check_digit(list(integer())) -> {ok, nil} |
{error, validation_error()}.
validate_check_digit(Digits) ->
case erlang:length(Digits) of
0 ->
{error, {invalid_length, 0}};
Len ->
Digits_without_check = gleam@list:take(Digits, Len - 1),
Actual_check = gl_gtin@internal@utils:last_digit(Digits),
case gl_gtin@check_digit:calculate(Digits_without_check) of
{ok, Expected_check} ->
case Actual_check =:= Expected_check of
true ->
{ok, nil};
false ->
{error, invalid_check_digit}
end;
{error, _} ->
{error, invalid_check_digit}
end
end.
-file("src/gl_gtin/validation.gleam", 169).
?DOC(
" Validate a GTIN code string.\n"
"\n"
" Checks that the input is a valid GTIN (8, 12, 13, or 14 digits) with a correct check digit.\n"
" Automatically trims leading and trailing whitespace before validation.\n"
"\n"
" # Arguments\n"
"\n"
" * `code` - GTIN string to validate\n"
"\n"
" # Returns\n"
"\n"
" Ok(GtinFormat) if valid, Error otherwise.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" validate(\"6291041500213\")\n"
" // -> Ok(Gtin13)\n"
"\n"
" validate(\"012345678905\")\n"
" // -> Ok(Gtin12)\n"
"\n"
" validate(\"invalid\")\n"
" // -> Error(InvalidCharacters)\n"
" ```\n"
).
-spec validate(binary()) -> {ok, gtin_format()} | {error, validation_error()}.
validate(Code) ->
Trimmed = gleam@string:trim(Code),
gleam@result:'try'(
parse_digits(Trimmed),
fun(Digits) ->
gleam@result:'try'(
validate_length(erlang:length(Digits)),
fun(Format) ->
gleam@result:'try'(
validate_check_digit(Digits),
fun(_) -> {ok, Format} end
)
end
)
end
).
-file("src/gl_gtin/validation.gleam", 207).
?DOC(
" Convert a GTIN-13 to GTIN-14 format.\n"
"\n"
" Prepends the indicator digit \"1\" and recalculates the check digit.\n"
" Only works with GTIN-13 codes; other formats return an error.\n"
"\n"
" # Arguments\n"
"\n"
" * `code` - GTIN-13 string to normalize\n"
"\n"
" # Returns\n"
"\n"
" Ok(gtin_14) if successful, Error otherwise.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" normalize(\"6291041500213\")\n"
" // -> Ok(\"16291041500214\")\n"
"\n"
" normalize(\"012345678905\")\n"
" // -> Error(InvalidFormat)\n"
" ```\n"
).
-spec normalize(binary()) -> {ok, binary()} | {error, validation_error()}.
normalize(Code) ->
gleam@result:'try'(validate(Code), fun(Format) -> case Format of
gtin13 ->
Without_check = gleam@string:slice(
Code,
0,
string:length(Code) - 1
),
With_indicator = <<"1"/utf8, Without_check/binary>>,
case gl_gtin@check_digit:generate(With_indicator) of
{ok, Gtin_14} ->
{ok, Gtin_14};
{error, _} ->
{error, invalid_format}
end;
_ ->
{error, invalid_format}
end end).