{"id":21967427,"url":"https://github.com/cayo-rodrigues/safe","last_synced_at":"2025-10-10T15:42:46.770Z","repository":{"id":252863365,"uuid":"836430904","full_name":"cayo-rodrigues/safe","owner":"cayo-rodrigues","description":"Simple validation library","archived":false,"fork":false,"pushed_at":"2025-07-25T12:29:31.000Z","size":318,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-25T19:01:20.752Z","etag":null,"topics":["library","tests","validation"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cayo-rodrigues.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-07-31T20:44:38.000Z","updated_at":"2025-07-25T12:29:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"6f561dbb-1a47-4483-9c36-e7f5b87ac7dc","html_url":"https://github.com/cayo-rodrigues/safe","commit_stats":null,"previous_names":["cayo-rodrigues/safe"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/cayo-rodrigues/safe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cayo-rodrigues%2Fsafe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cayo-rodrigues%2Fsafe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cayo-rodrigues%2Fsafe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cayo-rodrigues%2Fsafe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cayo-rodrigues","download_url":"https://codeload.github.com/cayo-rodrigues/safe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cayo-rodrigues%2Fsafe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004570,"owners_count":26083736,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["library","tests","validation"],"created_at":"2024-11-29T13:27:17.805Z","updated_at":"2025-10-10T15:42:46.764Z","avatar_url":"https://github.com/cayo-rodrigues.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Safe\n\n\u003e A simple Go validation library\n\n\u003cimg src=\"./logo.png\" width=\"20%\" alt=\"Safe logo\"\u003e\n\nUser input is unpredictable. Never trust it. Use this library to validate anything you want, and you know you're safe!\n\n## Installation\n\n```bash\ngo get -u github.com/cayo-rodrigues/safe\n```\n\n## Usage\n\n```go\nu := \u0026User{...}\n\noneYear := (time.Hour * 24) * 365\nminorBirthDate := time.Now().Add(-oneYear * 18)\nunavailableRoles := [2]string{\"software developer\", \"pepsimaaaaan\"}\npineappleRegex := regexp.MustCompile(\"^pine.*apple$\")\n\nfields := safe.Fields{\n    {\n        Name: \"username\",\n        Value: u.Username,\n        Rules: safe.Rules{safe.Required(), safe.Min(3), safe.Max(128)},\n    },\n    {\n        Name: \"email\",\n        Value: u.Email,\n        Rules: safe.Rules{safe.Required(), safe.Email(), safe.Max(128).WithMessage(\"Is your email really that long?\")},\n    },\n    {\n        Name: \"cpf/cnpj\",\n        Value: u.CpfCnpj,\n        Rules: safe.Rules{\n            safe.RequiredUnless(safe.All(u.Email, u.Username)), // cpf/cnpj is required, unless both u.Email and u.Username have a value\n            safe.CpfCnpj(),\n            safe.Max(128),\n        },\n    },\n    {\n        Name: \"password\",\n        Value: u.Password,\n        Rules: safe.Rules{safe.Required(), safe.StrongPassword()},\n    },\n    {\n        Name: \"roles\",\n        Value: u.Roles,\n        Rules: safe.Rules{\n            safe.UniqueList[string](), // must provide the type of the elements in the list\n            safe.NotOneOf(unavailableRoles[:]), // the input must be a slice, but unavailableRoles is an array with a fixed size, that's why we need [:] here\n        },\n    },\n    {\n        Name: \"birth\",\n        Value: u.BirthDate,\n        Rules: safe.Rules{\n            safe.RequiredUnless(u.CpfCnpj, u.Pineapple), // required, unless u.CpfCnpj OR u.Pineapple have a value\n            safe.NotBefore(minorBirthDate)},\n    },\n    {\n        Name: \"company_id\",\n        Value: u.CompanyID,\n        Rules: safe.Rules{\n            safe.RequiredUnless(safe.CnpjRegex.MatchString(u.CpfCnpj)).WithMessage(\"Must provide a valid cnpj or company_id\"), // got it?\n            safe.UUIDstr(),\n        },\n    },\n    {\n        Name: \"pineapple\",\n        Value: u.Pineapple,\n        Rules: safe.Rules{safe.Match(pineappleRegex)},\n    },\n}\n\n// this will set the language for all the error messages\n// the default is languages.PT_BR\nfields.SetLanguage(languages.EN_US)\n\nerrors, isValid := safe.Validate(fields)\n```\n\n\n\nThat's it! \n\nIn the example above, `errors` is a `safe.ErrorMessages`, which is just a wrapper around `map[string]string` that implements the `error` interface. It has error messages for each field. The default error message can be overwritten with the `WithMessage` func, as demonstrated in the example, for the email field.\n\nWhen a field fails to pass a given rule, no more subsequent rules are applied. For instance, if password is not provided, it will fail the `safe.Required` rule, hence the `safe.StrongPassword` rule will not run its validation func, and the resulting error message will be regarding the absence of a value, instead of the fact that it does not conform to a strong password standard.\n\nYou can refer to the source code or the individual documentation of each function for further instructions. They are all very intuitive.\n\n## Use cases\n\nThe fact that `safe.ErrorMessages` implements the `error` interface makes it possible to use it in any error handling case, just like any other error. \n\nFor example, suppose you have a custom error you return from an http api. You could do something like this:\n\n```go\ntype ApiError struct {\n\tStatusCode  int                `json:\"status_code\"`\n\tMsg         string             `json:\"msg\"`\n\tFieldErrors safe.ErrorMessages `json:\"field_errors\"`\n}\n\nfunc (e ApiError) Error() string {\n    // ...\n}\n```\n\nYou could also use it for unit testing:\n\n```go\n\nfunc TestInsertStuffService(t *testing.T) {\n    input := StuffInputData{\n        A: \"a\",\n        B: \"bb\",\n    }\n    output := services.InsertStuffService(\u0026input)\n\n    outputShape := safe.Fields{\n        {\n            Name: \"output_ID\",\n            Value: output.ID,\n            Rules: safe.Rules{safe.Required(), safe.UUIDstr()},\n        },\n        {\n            Name: \"output_A\",\n            Value: output.A,\n            Rules: safe.Rules{safe.Required(), safe.EqualTo(input.A)},\n        },\n        {\n            Name: \"output_B\",\n            Value: output.B,\n            Rules: safe.Rules{safe.Required(), safe.EqualTo(input.B)},\n        },\n        {\n            Name: \"output_CreatedAt\",\n            Value: output.CreatedtAt,\n            Rules: safe.Rules{safe.Required()},\n        }\n    }\n\n    errors, ok := safe.Validate(outputShape)\n    if !ok {\n        t.Fatalf(\"Output does not match expected conditions.\\nerrors: %s\\nvalue: %s\", errors, outputShape)\n    }\n}\n\n```\n\n## About error messages and languages\n\nSafe exposes `messages.Messages`, which is a localized set of error messages.\n\nYou can either extend the behavior of `messages.Messages` or create your own set of messages, completely decoupled from it.\n\nYou are free to use `messages.Messages` directly or to use the shortcuts provided in the `messages` package, like `messages.MandatoryFieldMsg`, `messages.MaxValueMsg`, and so forth.\nThe shortcuts provide a straightforward way to access a message, with an optional `language.Language` input.\n\nThe default language used in the error messages is `languages.PT_BR`, but you can use `languages.EN_US` as well. No other languages are supported out of the box right now, but nothing stops you from creating your own! \n\nHere is an example of what it may look like:\n\n```go\nmyLang := languages.Language(\"ES_LA\")\nmyMsgKey := messages.MessageKey(\"myMsgKey\")\n\nmyMsgPt := \"my msg PT\"\nmyMsgEn := \"my msg EN\"\nmyMsgInMyLang := \"my msg arriba!\"\n\nmessages.Messages.Update(messages.LocalizedMessages{\n    languages.PT_BR: {\n        myMsgKey: myMsgPt,\n    },\n    languages.EN_US: {\n        myMsgKey: myMsgEn,\n    },\n    myLang: {\n        myMsgKey: myMsgInMyLang,\n    },\n})\n\n// from here onwards you can use your new message from anywhere\nmsg := messages.Messages.Get(myLang, myMsgKey)\n```\n\nYou could also do something similar in case you want to just add a new language to the existing default messages.\nFor instance:\n\n```go\nnewLang := languages.Language(\"ES_LA\")\n\nmessages.Messages.Update(messages.LocalizedMessages{\n    newLang: {\n        messages.MsgKey__MandatoryField: \"Campo obligatorio\",\n        messages.MsgKey__InvalidFormat: \"Formato no válido\",\n        messages.MsgKey__UniqueList: \"Los valores en la lista deben ser únicos\",\n    },\n})\n\n// mandatory field message in ES_LA\nmsg1 := messages.MandatoryFieldMsg(newLang) \n\n// invalid format message in ES_LA\nmsg2 := messages.InvalidFormatMsg(newLang) \n\n// illogial dates message in PT_BR, because it has not been found in the ES_LA messages\nmsg3 := messages.IlogicalDatesMsg(newLang) \n```\n\n## Changing the default language\n\nThe default language is `languages.PT_BR`. To change it, do:\n\n```go\nmessages.DefaultLang = languages.EN_US\n```\n\nYou could also set it to some other language if you want:\n\n```go\nmyLang := languages.Language(\"ES_LA\")\nmessages.DefaultLang = myLang\n```\n\nBut in this case, remember that you must ensure that all messages have a fallback in `myLang`.\nIn case no message is found in any language at all, the final fallback is `\"T^T\"`. It will not panic.\n\n## Specific characteristics of rules\n\nAs already shown, rules can have their error message customized. But they can also be modified in other ways.\n\n```go\nfunc (rs *RuleSet) WithMessage(msg string) *RuleSet\nfunc (rs *RuleSet) WithMessageFunc(f func(*RuleSet) string) *RuleSet\nfunc (rs *RuleSet) WithValidateFunc(f func(*RuleSet) bool) *RuleSet\nfunc (rs *RuleSet) WithFlowFunc(f func(*RuleSet) bool) *RuleSet\nfunc (rs *RuleSet) WithOpts(opts *RuleSetOpts) *RuleSet\n```\n\n### About RuleSetOpts\n\nRules can be modified by setting options to them. Currently, there is only one available option.\n\n```go\ntype RuleSetOpts struct {\n    AcceptNumberZero bool\n}\n```\n\nIf this option is set to a rule, it will consider the number `0` as a non-zero value. For instance,\nif you have a `safe.Required` rule in a field, but this rule is configured with `AcceptNumberZero = true`,\nthen the number `0` will pass the rule, because it has a value.\n\n```go\nfields := safe.Fields{\n    {\n        Name: \"field_1\",\n        Value: 0,\n        Rules: safe.Rules{\n            safe.Required().WithOpts(\u0026safe.RuleSetOpts{\n                AcceptNumberZero: true,\n            }),\n        },\n    },\n}\n\n```\n\nIn order to make things easier, `safe.Fields` exposes methods to set rule opts.\n\n```go\nfunc (fields *Fields) SetRuleOpts(fieldNames []string, opts *RuleSetOpts) *Fields\nfunc (fields *Fields) SetRuleOptsForAll(opts *RuleSetOpts) *Fields\n```\n\n### About FlowFuncs\n\nA rule may have a flow function. This function, if present, will be executed during the validation routine, before the\nvalidation function. If the `FlowFunc` returns `true`, then proceed with the validation. Otherwise, stop validating the field.\nThere is no error message for this case. It simply stops validation.\n\nSome rules exposed by this library are purely flow rules, with no `ValidateFunc`.\n\nIf a rule has both `FlowFunc` and `ValidateFunc`, then `FlowFunc` will take preference.\n\n\n## Creating your own rules\n\nYou can also create your own rules. For instance:\n\n```go\nMyCustomRule := \u0026safe.RuleSet{\n    RuleName: \"my own rule!\", // this is used only for pretty printing, like fmt.Println(\"%s\", rs)\n    MessageFunc: func(rs *safe.RuleSet) string {\n        // here, you can return a message for when the input is not valid\n        // in case you create your own custom messages, this is the place where you could use them,\n        // passing rs.Language as input\n        return fmt.Sprintf(\"'%v'? Are you kidding?\", rs.FieldValue)\n    },\n    ValidateFunc: func(rs *safe.RuleSet) bool {\n        // in this function, you may perform any validation you want!\n        userInput, ok := rs.FieldValue.(string)\n        if !ok {\n            return false\n        }\n\n        if userInput == \"\" {\n            return true // in case you return false, the field will be required\n        }\n\n        isValid := false\n    \n        // perform checks...\n        \n        return isValid\n    },\n}\n\nsomeVal := \"someVal\"\n\nfields := safe.Fields{\n    // ...\n    {\n        Name: \"my custom rule\",\n        Value: someVal,\n        Rules: safe.Rules{MyCustomRule, safe.Max(256)},\n    }\n}\n```\n\n## All Rules\n\nThis is a list of all available rules. Hopefuly their names convey their behavior. Please refer to their individual documentations.\n\n- `safe.Required`\n- `safe.True`\n- `safe.False`\n- `safe.Email`\n- `safe.Phone`\n- `safe.Cpf`\n- `safe.Cnpj`\n- `safe.CpfCnpj`\n- `safe.CEP`\n- `safe.StrongPassword`\n- `safe.UUIDstr`\n- `safe.NoWhitespace`\n- `safe.UniqueList`\n- `safe.Match`\n- `safe.MatchList`\n- `safe.Min`\n- `safe.Max`\n- `safe.OneOf`\n- `safe.NotOneOf`\n- `safe.RequiredUnless`\n- `safe.RequiredIf`\n- `safe.After`\n- `safe.NotAfter`\n- `safe.Before`\n- `safe.NotBefore`\n- `safe.MaxDaysRange`\n- `safe.EqualTo`\n- `safe.NotEqualTo`\n- `safe.GreaterThan`\n- `safe.GreaterThanOrEqualTo`\n- `safe.LessThan`\n- `safe.LessThanOrEqualTo`\n- `safe.Contains`\n- `safe.NotContains`\n- `safe.ContainsAll`\n- `safe.ContainsSome`\n- `safe.ContainsNone`\n\n\n## Flow Rules\n\nFlow rules are used the same way as normal rules. However they control the flow of the validation. Hopefuly their names describe what they do.\n\n- `safe.StopIfNoValue`\n- `safe.StopIf`\n- `safe.StopIfFunc`\n\nHere is an example:\n\n```go\nfields := safe.Fields{\n    {\n        Name:  \"limit\",\n        Value: filters.Limit,\n        Rules: safe.Rules{safe.StopIfNoValue(), safe.GreaterThanOrEqualTo(10), safe.RequiredIf(filters.Offset)},\n    },\n    {\n        Name:  \"offset\",\n        Value: filters.Offset,\n        Rules: safe.Rules{safe.StopIfNoValue(), safe.GreaterThanOrEqualTo(0), safe.RequiredIf(filters.Limit)},\n    },\n    {\n        Name:  \"order_by\",\n        Value: filters.OrderBy,\n        Rules: safe.Rules{safe.OneOf([]string{\"created_at\"})},\n    },\n    {\n        Name:  \"search\",\n        Value: filters.Search,\n        Rules: safe.Rules{safe.Max(128)},\n    },\n}\n\nfields.SetRuleOptsForAll(\u0026safe.RuleSetOpts{\n    AcceptNumberZero: true,\n})\n```\n\nIn the example above, `filters.Limit` and `filters.Offset` will be validated only when they have a value. Otherwise, validation is skipped.\n\nAnd of course, you can create your own flow rules as well.\n\n## Helper functions\n\nSafe exposes some helper functions that you can use, whether in the context of validation rules or not. They are:\n\n- `safe.All`\n- `safe.AllFunc`\n- `safe.None`\n- `safe.NoneFunc`\n- `safe.Some`\n- `safe.SomeFunc`\n- `safe.HasValue`\n- `safe.HasValue__SkipNumeric`\n- `safe.AllUnique`\n- `safe.IsStrongPassword`\n- `safe.DifferenceInDays`\n- `safe.AnyToFloat64`\n\nPlease refer to their individual documentations.\n\n## Regexes\n\nSafe also exposes some regexes for convenience. They are:\n\n- `safe.WhateverRegex` (accepts literally anything)\n- `safe.EmailRegex`\n- `safe.PhoneRegex`\n- `safe.CpfRegex`\n- `safe.CnpjRegex`\n- `safe.CepRegex`\n- `safe.AddressNumberRegex`\n- `safe.UUIDRegex`\n- `safe.NoWhitespaceRegex`\n- `safe.HasUppercaseRegex`\n- `safe.HasLowercaseRegex`\n- `safe.HasDigitRegex`\n- `safe.HasSpecialCharacterRegex`\n\nPlease refer to their individual documentations.\n\nRegarding regexes, here is a useful repo: https://github.com/osintbrazuca/osint-brazuca-regex.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcayo-rodrigues%2Fsafe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcayo-rodrigues%2Fsafe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcayo-rodrigues%2Fsafe/lists"}