-
Notifications
You must be signed in to change notification settings - Fork 0
add validation package #17
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
Changes from 7 commits
1043391
af27e24
8c429fb
92eee4c
dc920b9
20f54d7
d291233
4321610
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@obosbbl/validation": minor | ||
| --- | ||
|
|
||
| initial release |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # @obosbbl/validation | ||
|
|
||
| [](https://www.npmjs.com/package/@obosbbl/validation) | ||
|
|
||
|
|
||
| A collection of validation methods for both 🇳🇴 and 🇸🇪 with zero dependencies. | ||
|
|
||
| ## Install | ||
|
|
||
| ```sh | ||
| # npm | ||
| npm install @obosbbl/validation | ||
|
|
||
| # pnpm | ||
| pnpm add @obosbbl/validation | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| The package has two entrypoints, one for `no` and one for `se`. That allows you to import for only the locale you need. | ||
|
|
||
|
|
||
| ```js | ||
| // 🇳🇴 example | ||
| import { validateOrganizationNumber } from '@obosbbl/validation/no'; | ||
| validateOrganizationNumber('937052766') // => true | ||
|
|
||
| validateOrganizationNumber('000') // => false | ||
|
|
||
| // 🇸🇪 example | ||
| import { validateOrganizationNumer } from '@obosbbl/validation/se'; | ||
| validateOrganizationNumber('5592221054') // => true | ||
|
|
||
| validateOrganizationNumber('000') // => false | ||
| ``` | ||
|
|
||
| ## Strictness and formatting characters | ||
|
|
||
| The methods are "strict" by default, meaning no formatting characters in the input is allowed. | ||
| This is preferrable, for instance when doing server-side validation, where the input is often expected to be a "clean" value. | ||
|
|
||
| If you want to allow formatting characters in the input, you can pass `allowFormatting: true` in the options object to the method. | ||
| Note that this currently allows any formatting characters, not just the just the "expected" ones for the input type. | ||
|
|
||
|
|
||
| ```js | ||
| import { validateOrganizationNumber } from '@obosbbl/validation/no'; | ||
|
|
||
| validateOrganizationNumber('937052766') // true | ||
| // formatting characters disallowed by default | ||
| validateOrganizationNumber('937 052 766') // false; | ||
| // allow formatting characters | ||
| validateOrganizationNumber('937 052 766', { allowFormatting: true }) // true; | ||
| ``` | ||
|
|
||
| ## Methods | ||
|
|
||
| * validatePostalCode | ||
| * validatePhoneNumber | ||
| * supports mobileOnly option | ||
| * validateOrganizationNumber | ||
| * Check digit verification is currently only implemented for Norwegian organization numbers. For Swedish organiation numbers, we only check the length of the input. PRs are welcome to fix this. | ||
|
|
||
| ## Example usage with Zod | ||
|
|
||
| ```js | ||
| import { z } from 'zod'; | ||
| import { validatePhoneNumber } from '@obosbbl/validation/no'; | ||
|
|
||
| const mobileOnlySchema = z.object({ | ||
| name: z.string(), | ||
| phoneNumber: z | ||
| .string() | ||
| .refine( | ||
| (val) => validatePhoneNumber(val, { mobileOnly: true }), | ||
| 'Telefonnummeret er ikke et gyldig mobilnummer', | ||
| ), | ||
| }); | ||
|
|
||
| const validData = { | ||
| name: 'Kari Nordmann', | ||
| phoneNumber: '92345678', | ||
| }; | ||
|
|
||
| mobileOnlySchema.parse(validData); // => { name: 'Kari Nordmann', phoneNumber: '92345678' } | ||
|
|
||
| const invalidData = { | ||
| name: 'Ola Nordmann', | ||
| phoneNumber: '22865500', | ||
| } | ||
|
|
||
| mobileOnlySchema.parse(invalidData); // => throws ZodError | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "@obosbbl/validation", | ||
| "version": "0.0.0", | ||
| "description": "A collection of validation methods for OBOS", | ||
| "repository": { | ||
| "url": "https://github.com/code-obos/public-frontend-modules" | ||
| }, | ||
| "license": "MIT", | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "exports": { | ||
| "./no": { | ||
| "types": "./dist/no.d.mts", | ||
| "default": "./dist/no.mjs" | ||
| }, | ||
| "./se": { | ||
| "types": "./dist/se.d.mts", | ||
| "default": "./dist/se.mjs" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "build": "bunchee" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import type { ValidatorOptions } from './types'; | ||
| import { mod11, stripFormatting } from './utils'; | ||
|
|
||
| type PostalCodeOptions = ValidatorOptions; | ||
|
|
||
| /** | ||
| * Validates that the input value is a Norwegian postal (zip) code. | ||
| * @example | ||
| * ``` | ||
| * validatePostalCode('0000') // => true | ||
| * ``` | ||
| */ | ||
| export function validatePostalCode( | ||
| value: string, | ||
| options: PostalCodeOptions = {}, | ||
| ): boolean { | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| return /^\d{4}$/.test(value); | ||
| } | ||
|
|
||
| type PhoneNumberOptions = ValidatorOptions & { | ||
| /** | ||
| * Whether it should be a mobile number | ||
| * @default false | ||
| */ | ||
| mobileOnly?: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Validates that the input value is a Norwegian phone number. | ||
| * | ||
| * Supports mobile only validation. | ||
| * @example | ||
| * ``` | ||
| * validatePhoneNumber('00000000') // => true | ||
| * validatePhoneNumber('90000000', { mobileOnly: true }) // => true | ||
| * ``` | ||
| */ | ||
| export function validatePhoneNumber( | ||
| value: string, | ||
| options: PhoneNumberOptions = {}, | ||
| ): boolean { | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| const isPhoneNumber = /^\d{8}$/.test(value); | ||
|
|
||
| if (options.mobileOnly) { | ||
| // Norwegian mobile phone numbers start with 4 or 9 | ||
| // See https://nkom.no/telefoni-og-telefonnummer/telefonnummer-og-den-norske-nummerplan/alle-nummerserier-for-norske-telefonnumre | ||
| return isPhoneNumber && ['4', '9'].includes(value.charAt(0)); | ||
| } | ||
|
|
||
| return isPhoneNumber; | ||
| } | ||
|
|
||
| type OrganizationNumberOptions = ValidatorOptions; | ||
|
|
||
| /** | ||
| * Validates that the input value is a {@link https://www.brreg.no/om-oss/registrene-vare/om-enhetsregisteret/organisasjonsnummeret/ Norwegian organization number}. | ||
| * @example | ||
| * ``` | ||
| * validateOrganizationNumber('000000000') // => true | ||
| * ``` | ||
| */ | ||
| export function validateOrganizationNumber( | ||
| value: string, | ||
| options: PhoneNumberOptions = {}, | ||
| ): boolean { | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| /** References: | ||
| * https://www.brreg.no/om-oss/registrene-vare/om-enhetsregisteret/organisasjonsnummeret/ | ||
| * https://no.wikipedia.org/wiki/Organisasjonsnummer | ||
| */ | ||
| return mod11(value, [3, 2, 7, 6, 5, 4, 3, 2]); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import type { ValidatorOptions } from './types'; | ||
| import { stripFormatting } from './utils'; | ||
|
|
||
| type PostalCodeOptions = ValidatorOptions; | ||
|
|
||
| /** | ||
| * Validates that the input value is a Swedish postal (zip) code. | ||
| * @example | ||
| * ``` | ||
| * validatePostalCode('00000') // => true | ||
| * ``` | ||
| */ | ||
| export function validatePostalCode( | ||
| value: string, | ||
| options: PostalCodeOptions = {}, | ||
| ): boolean { | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| return /^\d{5}$/.test(value); | ||
| } | ||
|
|
||
| type PhoneNumberOptions = ValidatorOptions & { | ||
| /** | ||
| * Whether it should be a mobile number | ||
| * @default false | ||
| */ | ||
| mobileOnly?: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Validates that the input value is a Swedish phone number. | ||
| * | ||
| * Supports mobile only validation. | ||
| * @example | ||
| * ``` | ||
| * validatePhoneNumber('00000000') // => true | ||
| * validatePhoneNumber('000000000') // => true | ||
| * validatePhoneNumber('0000000000') // => true | ||
| * validatePhoneNumber('0700000000', { mobileOnly: true }) // => true | ||
| * ``` | ||
| */ | ||
| export function validatePhoneNumber( | ||
| value: string, | ||
| options: PhoneNumberOptions = {}, | ||
| ): boolean { | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| if (options.mobileOnly) { | ||
| const isMobileNumber = /^07\d{8}$/.test(value); | ||
|
||
| return isMobileNumber; | ||
| } | ||
|
|
||
| const isPhoneNumber = /^0\d{7,9}$/.test(value); | ||
|
|
||
| return isPhoneNumber; | ||
| } | ||
|
|
||
| type OrganizationNumberOptions = ValidatorOptions; | ||
|
|
||
| /** | ||
| * Validates that the input value is a {@link https://www.skatteverket.se/foretagochorganisationer/foretagare/startaochregistrera/organisationsnummer.4.361dc8c15312eff6fd235d1.html Swedish organization number}. | ||
| * @example | ||
| * ``` | ||
| * validateOrganizationNumber('000000000') // => true | ||
| * ``` | ||
| */ | ||
| export function validateOrganizationNumber( | ||
| value: string, | ||
| options: OrganizationNumberOptions = {}, | ||
| ): boolean { | ||
| // TODO: Implement checksum validation. For now it only checks the number of digits. | ||
| if (options.allowFormatting) { | ||
| // biome-ignore lint/style/noParameterAssign: | ||
| value = stripFormatting(value); | ||
| } | ||
|
|
||
| return /^\d{10}$/.test(value); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export type ValidatorOptions = { | ||
| /** | ||
| * Allow formatting characters | ||
| * @default false | ||
| */ | ||
| allowFormatting?: boolean; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| /** Strip all formatting, leaving only numbers and letters */ | ||
| export function stripFormatting(value: string): string { | ||
| return value.replace(/[^a-zA-Z0-9]/g, ''); | ||
| } | ||
|
|
||
| /** | ||
| * Used to validate Norwegian bank account numbers, organization numbers and national identity numbers. | ||
| * See https://no.wikipedia.org/wiki/MOD11 | ||
| */ | ||
| export function mod11(value: string, weights: number[]): boolean { | ||
| // Since the control digit is a single value, the lengths should be equal | ||
| if (weights.length + 1 !== value.length) { | ||
| return false; | ||
| } | ||
|
|
||
| let sum = 0; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Krem eksempel på en liten
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Det skal sies at var opprinnelig en reduce faktisk, men reduce er en av de tingene jeg har sluttet å bruke. 9/10 ganger er ikke-reduce varianten mer lesbar, imo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hehe, må nok si meg uenig her ja 😆 |
||
| weights.forEach((weight, index) => { | ||
| sum += weight * Number(value[index]); | ||
| }); | ||
|
|
||
| let controlNumber = 11 - (sum % 11); | ||
|
|
||
| if (controlNumber === 11) { | ||
| controlNumber = 0; | ||
| } | ||
|
|
||
| return controlNumber === Number(value[value.length - 1]); | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.