apes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +82 -0
- data/.travis-gemfile +15 -0
- data/.travis.yml +15 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +22 -0
- data/README.md +177 -0
- data/Rakefile +44 -0
- data/apes.gemspec +34 -0
- data/doc/Apes.html +130 -0
- data/doc/Apes/Concerns.html +127 -0
- data/doc/Apes/Concerns/Errors.html +1089 -0
- data/doc/Apes/Concerns/Pagination.html +636 -0
- data/doc/Apes/Concerns/Request.html +766 -0
- data/doc/Apes/Concerns/Response.html +940 -0
- data/doc/Apes/Controller.html +1100 -0
- data/doc/Apes/Errors.html +125 -0
- data/doc/Apes/Errors/AuthenticationError.html +133 -0
- data/doc/Apes/Errors/BadRequestError.html +157 -0
- data/doc/Apes/Errors/BaseError.html +320 -0
- data/doc/Apes/Errors/InvalidDataError.html +157 -0
- data/doc/Apes/Errors/MissingDataError.html +157 -0
- data/doc/Apes/Model.html +378 -0
- data/doc/Apes/PaginationCursor.html +2138 -0
- data/doc/Apes/RuntimeConfiguration.html +909 -0
- data/doc/Apes/Serializers.html +125 -0
- data/doc/Apes/Serializers/JSON.html +389 -0
- data/doc/Apes/Serializers/JWT.html +452 -0
- data/doc/Apes/Serializers/List.html +347 -0
- data/doc/Apes/UrlsParser.html +1432 -0
- data/doc/Apes/Validators.html +125 -0
- data/doc/Apes/Validators/BaseValidator.html +278 -0
- data/doc/Apes/Validators/BooleanValidator.html +494 -0
- data/doc/Apes/Validators/EmailValidator.html +350 -0
- data/doc/Apes/Validators/PhoneValidator.html +375 -0
- data/doc/Apes/Validators/ReferenceValidator.html +372 -0
- data/doc/Apes/Validators/TimestampValidator.html +640 -0
- data/doc/Apes/Validators/UuidValidator.html +372 -0
- data/doc/Apes/Validators/ZipCodeValidator.html +372 -0
- data/doc/Apes/Version.html +189 -0
- data/doc/ApplicationController.html +547 -0
- data/doc/Concerns.html +128 -0
- data/doc/Concerns/ErrorHandling.html +826 -0
- data/doc/Concerns/PaginationHandling.html +463 -0
- data/doc/Concerns/RequestHandling.html +512 -0
- data/doc/Concerns/ResponseHandling.html +579 -0
- data/doc/Errors.html +126 -0
- data/doc/Errors/AuthenticationError.html +123 -0
- data/doc/Errors/BadRequestError.html +147 -0
- data/doc/Errors/BaseError.html +289 -0
- data/doc/Errors/InvalidDataError.html +147 -0
- data/doc/Errors/MissingDataError.html +147 -0
- data/doc/Model.html +315 -0
- data/doc/PaginationCursor.html +764 -0
- data/doc/Serializers.html +126 -0
- data/doc/Serializers/JSON.html +253 -0
- data/doc/Serializers/JWT.html +253 -0
- data/doc/Serializers/List.html +245 -0
- data/doc/Validators.html +126 -0
- data/doc/Validators/BaseValidator.html +209 -0
- data/doc/Validators/BooleanValidator.html +391 -0
- data/doc/Validators/EmailValidator.html +298 -0
- data/doc/Validators/PhoneValidator.html +313 -0
- data/doc/Validators/ReferenceValidator.html +284 -0
- data/doc/Validators/TimestampValidator.html +476 -0
- data/doc/Validators/UuidValidator.html +310 -0
- data/doc/Validators/ZipCodeValidator.html +310 -0
- data/doc/_index.html +435 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +252 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +252 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +615 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/apes.rb +40 -0
- data/lib/apes/concerns/errors.rb +111 -0
- data/lib/apes/concerns/pagination.rb +81 -0
- data/lib/apes/concerns/request.rb +237 -0
- data/lib/apes/concerns/response.rb +74 -0
- data/lib/apes/controller.rb +77 -0
- data/lib/apes/errors.rb +38 -0
- data/lib/apes/model.rb +94 -0
- data/lib/apes/pagination_cursor.rb +152 -0
- data/lib/apes/runtime_configuration.rb +80 -0
- data/lib/apes/serializers.rb +88 -0
- data/lib/apes/urls_parser.rb +233 -0
- data/lib/apes/validators.rb +234 -0
- data/lib/apes/version.rb +24 -0
- data/spec/apes/concerns/errors_spec.rb +141 -0
- data/spec/apes/concerns/pagination_spec.rb +114 -0
- data/spec/apes/concerns/request_spec.rb +244 -0
- data/spec/apes/concerns/response_spec.rb +79 -0
- data/spec/apes/controller_spec.rb +54 -0
- data/spec/apes/errors_spec.rb +14 -0
- data/spec/apes/models_spec.rb +148 -0
- data/spec/apes/pagination_cursor_spec.rb +113 -0
- data/spec/apes/runtime_configuration_spec.rb +100 -0
- data/spec/apes/serializers_spec.rb +70 -0
- data/spec/apes/urls_parser_spec.rb +150 -0
- data/spec/apes/validators_spec.rb +237 -0
- data/spec/spec_helper.rb +30 -0
- data/views/_included.json.jbuilder +9 -0
- data/views/_pagination.json.jbuilder +9 -0
- data/views/collection.json.jbuilder +4 -0
- data/views/errors/400.json.jbuilder +9 -0
- data/views/errors/403.json.jbuilder +7 -0
- data/views/errors/404.json.jbuilder +6 -0
- data/views/errors/422.json.jbuilder +19 -0
- data/views/errors/500.json.jbuilder +12 -0
- data/views/errors/501.json.jbuilder +7 -0
- data/views/layouts/general.json.jbuilder +36 -0
- data/views/object.json.jbuilder +4 -0
- metadata +262 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# A set of common serializers.
|
|
8
|
+
module Serializers
|
|
9
|
+
# Comma separated serialized value.
|
|
10
|
+
class List
|
|
11
|
+
# Loads serialized data.
|
|
12
|
+
#
|
|
13
|
+
# @param data [String] The serialized data.
|
|
14
|
+
# @return [Array] A array of values.
|
|
15
|
+
def self.load(data)
|
|
16
|
+
return data if data.is_a?(Array)
|
|
17
|
+
data.ensure_string.tokenize
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Serializes data.
|
|
21
|
+
#
|
|
22
|
+
# @param data [Object] The data to serialize.
|
|
23
|
+
# @return [String] Serialized data.
|
|
24
|
+
def self.dump(data)
|
|
25
|
+
data.ensure_array.compact.map(&:to_s).join(",")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# JSON encoded serialized value.
|
|
30
|
+
class JSON
|
|
31
|
+
# Saves serialized data.
|
|
32
|
+
#
|
|
33
|
+
# @param data [String] The serialized data.
|
|
34
|
+
# @param raise_errors [Boolean] Whether to raise decoding errors.
|
|
35
|
+
# @param default [Object] A fallback value to return when not raising errors.
|
|
36
|
+
# @return [Object] A deserialized value.
|
|
37
|
+
def self.load(data, raise_errors = false, default = {})
|
|
38
|
+
data = ActiveSupport::JSON.decode(data)
|
|
39
|
+
data = data.with_indifferent_access if data.is_a?(Hash)
|
|
40
|
+
data
|
|
41
|
+
rescue => e
|
|
42
|
+
raise(e) if raise_errors
|
|
43
|
+
default
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Saves serialized data.
|
|
47
|
+
#
|
|
48
|
+
# @param data [Object] The data to serialize.
|
|
49
|
+
# @return [String] Serialized data.
|
|
50
|
+
def self.dump(data)
|
|
51
|
+
ActiveSupport::JSON.encode(data.as_json)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# JWT encoded serialized value.
|
|
56
|
+
class JWT
|
|
57
|
+
class << self
|
|
58
|
+
# Loads serialized data.
|
|
59
|
+
#
|
|
60
|
+
# @param serialized [String] The serialized data.
|
|
61
|
+
# @param raise_errors [Boolean] Whether to raise decoding errors.
|
|
62
|
+
# @param default [Object] A fallback value to return when not raising errors.
|
|
63
|
+
# @return [Object] A deserialized value.
|
|
64
|
+
def load(serialized, raise_errors = false, default = {})
|
|
65
|
+
data = ::JWT.decode(serialized, jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "data"}).dig(0, "sub")
|
|
66
|
+
data = data.with_indifferent_access if data.is_a?(Hash)
|
|
67
|
+
data
|
|
68
|
+
rescue => e
|
|
69
|
+
raise(e) if raise_errors
|
|
70
|
+
default
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Saves serialized data.
|
|
74
|
+
#
|
|
75
|
+
# @param data [Object] The data to serialize.
|
|
76
|
+
# @return [String] Serialized data.
|
|
77
|
+
def dump(data)
|
|
78
|
+
::JWT.encode({aud: "data", sub: data.as_json}, jwt_secret, "HS256")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#:nodoc:
|
|
82
|
+
def jwt_secret
|
|
83
|
+
Apes::RuntimeConfiguration.jwt_token
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# Utility class to parse URLs, domains and emails.
|
|
8
|
+
class UrlsParser
|
|
9
|
+
# The list of valid top level domains for a URL, email and domain.
|
|
10
|
+
# To update the list: http:jecas.cz/tld-list/
|
|
11
|
+
TLDS = [
|
|
12
|
+
"ac", "academy", "accountants", "actor", "ad", "ae", "aero", "af", "ag", "agency", "ai", "airforce", "al", "am", "an", "ao", "aq", "ar", "archi", "arpa",
|
|
13
|
+
"as", "asia", "associates", "at", "attorney", "au", "audio", "autos", "aw", "ax", "axa", "az",
|
|
14
|
+
"ba", "bar", "bargains", "bayern", "bb", "bd", "be", "beer", "berlin", "best", "bf", "bg", "bh", "bi", "bid", "bike", "bio", "biz", "bj", "black",
|
|
15
|
+
"blackfriday", "blue", "bm", "bn", "bo", "boutique", "br", "bs", "bt", "build", "builders", "buzz", "bv", "bw", "by", "bz",
|
|
16
|
+
"ca", "cab", "camera", "camp", "capital", "cards", "care", "career", "careers", "cash", "cat", "catering", "cc", "cd", "center", "ceo", "cf", "cg", "ch",
|
|
17
|
+
"cheap", "christmas", "church", "ci", "citic", "ck", "cl", "claims", "cleaning", "clinic", "clothing", "club", "cm", "cn", "co", "codes", "coffee",
|
|
18
|
+
"college", "cologne", "com", "community", "company", "computer", "condos", "construction", "consulting", "contractors", "cooking", "cool", "coop",
|
|
19
|
+
"country", "cr", "credit", "creditcard", "cruises", "cu", "cv", "cw", "cx", "cy", "cz",
|
|
20
|
+
"dance", "dating", "de", "dev", "degree", "democrat", "dental", "dentist", "desi", "diamonds", "digital",
|
|
21
|
+
"directory", "discount", "dj", "dk", "dm", "dnp", "do", "domains", "dz",
|
|
22
|
+
"ec", "edu", "education", "ee", "eg", "email", "engineering", "enterprises", "equipment", "er", "es", "estate", "et", "eu", "eus", "events", "exchange",
|
|
23
|
+
"expert", "exposed",
|
|
24
|
+
"fail", "farm", "feedback", "fi", "finance", "financial", "fish", "fishing", "fitness", "fj", "fk", "flights", "florist", "fm", "fo", "foo", "foundation",
|
|
25
|
+
"fr", "frogans", "fund", "furniture", "futbol",
|
|
26
|
+
"ga", "gal", "gallery", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gift", "gl", "glass", "globo", "gm", "gmo", "gn", "gop", "gov", "gp", "gq", "gr",
|
|
27
|
+
"graphics", "gratis", "gripe", "gs", "gt", "gu", "guide", "guitars", "guru", "gw", "gy",
|
|
28
|
+
"haus", "hiphop", "hiv", "hk", "hm", "hn", "holdings", "holiday", "homes", "horse", "host", "house", "hr", "ht", "hu",
|
|
29
|
+
"id", "ie", "il", "im", "immobilien", "in", "industries", "info", "ink", "institute", "insure", "int", "international", "investments", "io", "iq", "ir",
|
|
30
|
+
"is", "it",
|
|
31
|
+
"je", "jetzt", "jm", "jo", "jobs", "jp", "juegos",
|
|
32
|
+
"kaufen", "ke", "kg", "kh", "ki", "kim", "kitchen", "kiwi", "km", "kn", "koeln", "kp", "kr", "kred", "kw", "ky", "kz",
|
|
33
|
+
"la", "land", "lawyer", "lb", "lc", "lease", "li", "life", "lighting", "limited", "limo", "link", "lk", "loans", "london", "lr", "ls", "lt", "lu", "luxe",
|
|
34
|
+
"luxury", "lv", "ly",
|
|
35
|
+
"ma", "maison", "management", "mango", "market", "marketing", "mc", "md", "me", "media", "meet", "menu", "mg", "mh", "miami", "mil", "mk", "ml", "mm",
|
|
36
|
+
"mn", "mo", "mobi", "moda", "moe", "monash", "mortgage", "moscow", "motorcycles", "mp", "mq", "mr", "ms", "mt", "mu", "museum", "mv", "mw", "mx", "my",
|
|
37
|
+
"mz", "na", "nagoya", "name", "nc", "ne", "net", "neustar", "nf", "ng", "ni", "ninja", "nl", "no", "np", "nr", "nu", "nyc", "nz",
|
|
38
|
+
"okinawa", "om", "onl", "org",
|
|
39
|
+
"pa", "paris", "partners", "parts", "pe", "pf", "pg", "ph", "photo", "photography", "photos", "pics", "pictures", "pink", "pk", "pl", "plumbing", "pm",
|
|
40
|
+
"pn", "post", "pr", "press", "pro", "productions", "properties", "ps", "pt", "pub", "pw", "py",
|
|
41
|
+
"qa", "qpon", "quebec",
|
|
42
|
+
"re", "recipes", "red", "reise", "reisen", "ren", "rentals", "repair", "report", "rest", "reviews", "rich", "rio", "ro", "rocks", "rodeo", "rs", "ru",
|
|
43
|
+
"ruhr", "rw", "ryukyu",
|
|
44
|
+
"sa", "saarland", "sb", "sc", "schule", "sd", "se", "services", "sexy", "sg", "sh", "shiksha", "shoes", "si", "singles", "sj", "sk", "sl", "sm", "sn",
|
|
45
|
+
"so", "social", "software", "sohu", "solar", "solutions", "soy", "space", "sr", "st", "su", "supplies", "supply", "support", "surgery", "sv", "sx", "sy",
|
|
46
|
+
"systems", "sz",
|
|
47
|
+
"tattoo", "tax", "tc", "td", "technology", "tel", "tf", "tg", "th", "tienda", "tips", "tj", "tk", "tl", "tm", "tn", "to", "today", "tokyo", "tools",
|
|
48
|
+
"town", "toys", "tp", "tr", "trade", "training", "travel", "tt", "tv", "tw", "tz",
|
|
49
|
+
"ua", "ug", "uk", "university", "uno", "us", "uy", "uz",
|
|
50
|
+
"va", "vacations", "vc", "ve", "vegas", "ventures", "versicherung", "vet", "vg", "vi", "viajes", "villas", "vision", "vn", "vodka", "vote", "voting",
|
|
51
|
+
"voto", "voyage", "vu",
|
|
52
|
+
"wang", "watch", "webcam", "website", "wed", "wf", "wien", "wiki", "works", "ws", "wtc", "wtf",
|
|
53
|
+
"xn--3bst00m", "xn--3ds443g", "xn--3e0b707e", "xn--45brj9c", "xn--4gbrim", "xn--55qw42g", "xn--55qx5d", "xn--6frz82g", "xn--6qq986b3xl", "xn--80adxhks",
|
|
54
|
+
"xn--80ao21a", "xn--80asehdb", "xn--80aswg", "xn--90a3ac", "xn--c1avg", "xn--cg4bki", "xn--clchc0ea0b2g2a9gcd", "xn--czr694b", "xn--czru2d",
|
|
55
|
+
"xn--d1acj3b", "xn--fiq228c5hs", "xn--fiq64b", "xn--fiqs8s", "xn--fiqz9s", "xn--fpcrj9c3d", "xn--fzc2c9e2c", "xn--gecrj9c", "xn--h2brj9c",
|
|
56
|
+
"xn--i1b6b1a6a2e", "xn--io0a7i", "xn--j1amh", "xn--j6w193g", "xn--kprw13d", "xn--kpry57d", "xn--l1acc", "xn--lgbbat1ad8j", "xn--mgb9awbf",
|
|
57
|
+
"xn--mgba3a4f16a", "xn--mgbaam7a8h", "xn--mgbab2bd", "xn--mgbayh7gpa", "xn--mgbbh1a71e", "xn--mgbc0a9azcg", "xn--mgberp4a5d4ar", "xn--mgbx4cd0ab",
|
|
58
|
+
"xn--ngbc5azd", "xn--nqv7f", "xn--nqv7fs00ema", "xn--o3cw4h", "xn--ogbpf8fl", "xn--p1ai", "xn--pgbs0dh", "xn--q9jyb4c", "xn--rhqv96g", "xn--s9brj9c",
|
|
59
|
+
"xn--ses554g", "xn--unup4y", "xn--wgbh1c", "xn--wgbl6a", "xn--xkc2al3hye2a", "xn--xkc2dl3a5ee0h", "xn--yfro4i67o", "xn--ygbi2ammx", "xn--zfr164b", "xxx",
|
|
60
|
+
"xyz",
|
|
61
|
+
"yachts", "ye", "yokohama", "yt",
|
|
62
|
+
"za", "zm", "zone", "zw"
|
|
63
|
+
].freeze
|
|
64
|
+
|
|
65
|
+
# Regular expression to match a valid URL.
|
|
66
|
+
URL_MATCHER = /
|
|
67
|
+
(
|
|
68
|
+
(http(s?):\/\/)? #PROTOCOL
|
|
69
|
+
(
|
|
70
|
+
(#{Resolv::IPv4::Regex.source.gsub("\\A", "").gsub("\\z", "")}) # IPv4
|
|
71
|
+
|
|
|
72
|
+
(\[(#{Resolv::IPv6::Regex.source.gsub("\\A", "").gsub("\\z", "")})\]) # IPv6
|
|
73
|
+
|
|
|
74
|
+
(
|
|
75
|
+
(([\w\-\_]+\.)*) # LOWEST TLD
|
|
76
|
+
([\w\-\_]+) # 2nd LEVEL TLD
|
|
77
|
+
(\.(#{TLDS.join("|")})) # TOP TLD
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
((:\d+)?) # PORT
|
|
81
|
+
((\/\S*)?) # PATH
|
|
82
|
+
((\?\S+)?) # QUERY
|
|
83
|
+
((\#\S*)?) # FRAGMENT
|
|
84
|
+
)
|
|
85
|
+
/ix
|
|
86
|
+
|
|
87
|
+
# Regular expression to detect a URL in a text.
|
|
88
|
+
URL_SEPARATOR = /[\[\]!"#$%&'()*+,.:;<=>?~\s@\^_`\{|\}\-]/
|
|
89
|
+
|
|
90
|
+
# Regular expression to match a valid email address.
|
|
91
|
+
EMAIL_MATCHER = /
|
|
92
|
+
^(
|
|
93
|
+
([\w\+\-\_\.\']+@) #HOST
|
|
94
|
+
(([\w\-]+\.)*) # LOWEST TLD
|
|
95
|
+
([\w\-_]+) # 2nd LEVEL TLD
|
|
96
|
+
(\.(#{TLDS.join("|")})) # TOP TLD
|
|
97
|
+
)$
|
|
98
|
+
/ix
|
|
99
|
+
|
|
100
|
+
# Regular expression to match a valid domain.
|
|
101
|
+
DOMAIN_MATCHER = /
|
|
102
|
+
^(
|
|
103
|
+
(([\w\-]+\.)*) # LOWEST TLD
|
|
104
|
+
([\w\-_]+) # 2nd LEVEL TLD
|
|
105
|
+
(\.(#{TLDS.join("|")})) # TOP TLD
|
|
106
|
+
)$
|
|
107
|
+
/ix
|
|
108
|
+
|
|
109
|
+
# Template to replace URLs in a text.
|
|
110
|
+
TEMPLATE = "{{urls.url_%s}}".freeze
|
|
111
|
+
|
|
112
|
+
# Get the singleton instance of the parser.
|
|
113
|
+
#
|
|
114
|
+
# @param force [Boolean] Whether to force creation of a new singleton.
|
|
115
|
+
# @return [Apes::UrlsParser] A instance of the parser.
|
|
116
|
+
def self.instance(force = false)
|
|
117
|
+
@instance = nil if force
|
|
118
|
+
@instance ||= new
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Checks if the value is a valid URL.
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean] `true` if the value is a valid URL, `false` otherwise.
|
|
124
|
+
def url?(url)
|
|
125
|
+
url.strip =~ /^(#{UrlsParser::URL_MATCHER.source})$/ix ? true : false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Checks if the value is a valid email address.
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean] `true` if the value is a valid email address, `false` otherwise.
|
|
131
|
+
def email?(email)
|
|
132
|
+
email.strip =~ /^(#{UrlsParser::EMAIL_MATCHER.source})$/ix ? true : false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Checks if the value is a valid domain.
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] `true` if the value is a valid domain, `false` otherwise.
|
|
138
|
+
def domain?(domain)
|
|
139
|
+
domain.strip =~ /^(#{UrlsParser::DOMAIN_MATCHER.source})$/ix ? true : false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Checks if the value is a shortened URL according to the provided shortened domains.
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean] `true` if the value is a shortend URL, `false` otherwise.
|
|
145
|
+
def shortened?(url, *shortened_domains)
|
|
146
|
+
domains = ["bit.ly"].concat(shortened_domains).uniq.compact.map(&:strip)
|
|
147
|
+
url?(url) && (ensure_url_with_scheme(url.strip) =~ /^(http(s?):\/\/(#{domains.map { |d| Regexp.quote(d) }.join("|")}))/i ? true : false)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Makes sure the string starts with the scheme for the specified protocol.
|
|
151
|
+
#
|
|
152
|
+
# @param subject [String] The string to analyze.
|
|
153
|
+
# @param protocol [String] The protocol for the URL.
|
|
154
|
+
# @param secure [Boolean] If the scheme should be secure or not.
|
|
155
|
+
# @return [String] The string with a URL scheme at the beginning.
|
|
156
|
+
def ensure_url_with_scheme(subject, protocol = "http", secure: false)
|
|
157
|
+
schema = protocol + (secure ? "s" : "")
|
|
158
|
+
subject !~ /^(#{protocol}(s?):\/\/)/ ? "#{schema}://#{subject}" : subject
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Extract all URLS from a text.
|
|
162
|
+
#
|
|
163
|
+
# @param text [String] The text that contains URLs.
|
|
164
|
+
# @param mode [Symbol] Which URLs to extract. It can be `:shortened`, `:unshortened` or `:all` (the default).
|
|
165
|
+
# @param sort [NilClass|Symbol] If not `nil`, how to sort extracted URLs. It can be `:asc` or `:desc`.
|
|
166
|
+
# @param shortened_domains [Array] Which domains to consider shortened.
|
|
167
|
+
# @return [Array] An array of extracted URLs.
|
|
168
|
+
def extract_urls(text, mode: :all, sort: nil, shortened_domains: [])
|
|
169
|
+
regexp = /((^|\s+)(?<url>#{UrlsParser::URL_MATCHER.source})(#{UrlsParser::URL_SEPARATOR.source}|$))/ix
|
|
170
|
+
matches = text.scan(regexp).flatten.map { |u| clean(u) }.uniq
|
|
171
|
+
|
|
172
|
+
if mode == :shortened
|
|
173
|
+
matches.select! { |u| shortened?(u, *shortened_domains) }
|
|
174
|
+
elsif mode == :unshortened
|
|
175
|
+
matches.reject! { |u| shortened?(u, *shortened_domains) }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
matches = sort_urls(matches, sort)
|
|
179
|
+
matches
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Replace all URLs in a text with provided replacements.
|
|
183
|
+
#
|
|
184
|
+
# @param text [String] The text that contains URLs.
|
|
185
|
+
# @param replacements [Hash] A map where keys are the URLs to replace and values are their replacements.
|
|
186
|
+
# @param mode [Symbol] Which URLs to extract. It can be `:shortened`, `:unshortened` or `:all` (the default).
|
|
187
|
+
# @param shortened_domains [Array] Which domains to consider shortened.
|
|
188
|
+
# @return [String] The original text with all URLs replaced.
|
|
189
|
+
def replace_urls(text, replacements: {}, mode: :all, shortened_domains: [])
|
|
190
|
+
text = text.dup
|
|
191
|
+
|
|
192
|
+
urls = extract_urls(text, mode: mode, sort: :desc, shortened_domains: shortened_domains).reduce({}) do |accu, url|
|
|
193
|
+
if replacements[url]
|
|
194
|
+
hash = hashify(url)
|
|
195
|
+
accu["url_#{hash}"] = ensure_url_with_scheme(replacements[url])
|
|
196
|
+
text.gsub!(/#{Regexp.quote(url)}/, format(UrlsParser::TEMPLATE, hash))
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
accu
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
Mustache.render(text, urls: urls)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Removes all extra characters (like trailing comma) from a URL.
|
|
206
|
+
#
|
|
207
|
+
# @param url [String] The URL to clean.
|
|
208
|
+
# @return [String] The cleaned URL.
|
|
209
|
+
def clean(url)
|
|
210
|
+
url.strip.gsub(/#{UrlsParser::URL_SEPARATOR.source}$/, "")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Generate a hash of a URL.
|
|
214
|
+
#
|
|
215
|
+
# @param url [String] The URL to hashify.
|
|
216
|
+
# @return [String] The hash for the URL.
|
|
217
|
+
def hashify(url)
|
|
218
|
+
Digest::SHA2.hexdigest(ensure_url_with_scheme(url.strip))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# :nodoc:
|
|
224
|
+
def sort_urls(matches, sort)
|
|
225
|
+
if sort
|
|
226
|
+
matches.sort! { |u1, u2| u1.length <=> u2.length }
|
|
227
|
+
matches.reverse! if sort == :desc
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
matches
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
module Apes
|
|
7
|
+
# A useful set of validators.
|
|
8
|
+
module Validators
|
|
9
|
+
# The base validator.
|
|
10
|
+
class BaseValidator < ActiveModel::EachValidator
|
|
11
|
+
# Perform validation on a attribute of a model.
|
|
12
|
+
#
|
|
13
|
+
# @param model [Object] The object to validate.
|
|
14
|
+
# @param attribute [String|Symbol] The attribute to validate.
|
|
15
|
+
# @param value [Object] The value of the attribute.
|
|
16
|
+
def validate_each(model, attribute, value)
|
|
17
|
+
checked = check_valid?(value)
|
|
18
|
+
return checked if checked
|
|
19
|
+
|
|
20
|
+
message = options[:message] || options[:default_message]
|
|
21
|
+
destination = options[:additional] ? model.additional_errors : model.errors
|
|
22
|
+
destination[attribute] << message
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Validates references (relationships in the JSON API nomenclature).
|
|
28
|
+
class ReferenceValidator < BaseValidator
|
|
29
|
+
# Creates a new validator.
|
|
30
|
+
#
|
|
31
|
+
# @param options [Hash] The options for the validations.
|
|
32
|
+
# @return [Apes::Validators::ReferenceValidator] A new validator.
|
|
33
|
+
def initialize(options)
|
|
34
|
+
@class_name = options[:class_name]
|
|
35
|
+
label = options[:label] || options[:class_name].classify
|
|
36
|
+
super(options.reverse_merge(default_message: "must be a valid #{label} (cannot find a #{label} with id \"%s\")"))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Perform validation on a attribute of a model.
|
|
40
|
+
#
|
|
41
|
+
# @param model [Object] The object to validate.
|
|
42
|
+
# @param attribute [String|Symbol] The attribute to validate.
|
|
43
|
+
# @param values [Array] The values of the attribute.
|
|
44
|
+
def validate_each(model, attribute, values)
|
|
45
|
+
values = Serializers::JSON.load(values, false, values)
|
|
46
|
+
|
|
47
|
+
values.ensure_array.each do |value|
|
|
48
|
+
checked = @class_name.classify.constantize.find_with_any(value)
|
|
49
|
+
add_failure(attribute, model, value) unless checked
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# :nodoc:
|
|
56
|
+
def add_failure(attribute, record, value)
|
|
57
|
+
message = options[:message] || options[:default_message]
|
|
58
|
+
destination = options[:additional] ? record.additional_errors : record.errors
|
|
59
|
+
destination[attribute] << sprintf(message, value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Validates UUIDs (version 4).
|
|
64
|
+
class UuidValidator < BaseValidator
|
|
65
|
+
# The pattern to recognized a valid UUID version 4.
|
|
66
|
+
VALID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
|
67
|
+
|
|
68
|
+
# Creates a new validator.
|
|
69
|
+
#
|
|
70
|
+
# @param options [Hash] The options for the validations.
|
|
71
|
+
# @return [Apes::Validators::UuidValidator] A new validator.
|
|
72
|
+
def initialize(options)
|
|
73
|
+
super(options.reverse_merge(default_message: "must be a valid UUID"))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Checks if the value is valid for this validator.
|
|
77
|
+
#
|
|
78
|
+
# @param value [Object] The value to validate.
|
|
79
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
80
|
+
def check_valid?(value)
|
|
81
|
+
value.blank? || value =~ VALID_REGEX
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validates email.
|
|
86
|
+
class EmailValidator < BaseValidator
|
|
87
|
+
# Creates a new validator.
|
|
88
|
+
#
|
|
89
|
+
# @param options [Hash] The options for the validations.
|
|
90
|
+
# @return [Apes::Validators::EmailValidator] A new validator.
|
|
91
|
+
def initialize(options)
|
|
92
|
+
super(options.reverse_merge(default_message: "must be a valid email"))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Checks if the value is valid for this validator.
|
|
96
|
+
#
|
|
97
|
+
# @param value [Object] The value to validate.
|
|
98
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
99
|
+
def check_valid?(value)
|
|
100
|
+
value.blank? || UrlsParser.instance.email?(value.ensure_string)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Validates boolean values.
|
|
105
|
+
class BooleanValidator < BaseValidator
|
|
106
|
+
# Parses a boolean value.
|
|
107
|
+
#
|
|
108
|
+
# @param value [Object] The value to parse.
|
|
109
|
+
# @param raise_errors [Boolean] Whether to raise errors in case the value couldn't be parsed.
|
|
110
|
+
# @return [Boolean|NilClass] A boolean value if parsing succeded, `nil` otherwise.
|
|
111
|
+
def self.parse(value, raise_errors: false)
|
|
112
|
+
raise(ArgumentError, "Invalid boolean value \"#{value}\".") if !value.nil? && !value.boolean? && raise_errors
|
|
113
|
+
value.to_boolean
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Creates a new validator.
|
|
117
|
+
#
|
|
118
|
+
# @param options [Hash] The options for the validations.
|
|
119
|
+
# @return [Apes::Validators::BooleanValidator] A new validator.
|
|
120
|
+
def initialize(options)
|
|
121
|
+
super(options.reverse_merge(default_message: "must be a valid truthy/falsey value"))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Checks if the value is valid for this validator.
|
|
125
|
+
#
|
|
126
|
+
# @param value [Object] The value to validate.
|
|
127
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
128
|
+
def check_valid?(value)
|
|
129
|
+
value.blank? || value.boolean?
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Validates phones.
|
|
134
|
+
class PhoneValidator < BaseValidator
|
|
135
|
+
# The pattern to recognize valid phones.
|
|
136
|
+
VALID_REGEX = /^(
|
|
137
|
+
((\+|00)\d)? # International prefix
|
|
138
|
+
([0-9\-\s\/\(\)]{7,}) # All the rest
|
|
139
|
+
)$/mx
|
|
140
|
+
|
|
141
|
+
# Creates a new validator.
|
|
142
|
+
#
|
|
143
|
+
# @param options [Hash] The options for the validations.
|
|
144
|
+
# @return [Apes::Validators::PhoneValidator] A new validator.
|
|
145
|
+
def initialize(options)
|
|
146
|
+
super(options.reverse_merge(default_message: "must be a valid phone"))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Checks if the value is valid for this validator.
|
|
150
|
+
#
|
|
151
|
+
# @param value [Object] The value to validate.
|
|
152
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
153
|
+
def check_valid?(value)
|
|
154
|
+
value.blank? || value =~ VALID_REGEX
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Validates ZIP codes.
|
|
159
|
+
class ZipCodeValidator < BaseValidator
|
|
160
|
+
# The pattern to recognized valid ZIP codes.
|
|
161
|
+
VALID_REGEX = /^(\d{5}(-\d{1,4})?)$/
|
|
162
|
+
|
|
163
|
+
# Creates a new validator.
|
|
164
|
+
#
|
|
165
|
+
# @param options [Hash] The options for the validations.
|
|
166
|
+
# @return [Apes::Validators::ZipCodeValidator] A new validator.
|
|
167
|
+
def initialize(options)
|
|
168
|
+
super(options.reverse_merge(default_message: "must be a valid ZIP code"))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Checks if the value is valid for this validator.
|
|
172
|
+
#
|
|
173
|
+
# @param value [Object] The value to validate.
|
|
174
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
175
|
+
def check_valid?(value)
|
|
176
|
+
value.blank? || value =~ VALID_REGEX
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Validates timestamps.
|
|
181
|
+
class TimestampValidator < BaseValidator
|
|
182
|
+
# Parses a timestamp value according to a list of formats.
|
|
183
|
+
#
|
|
184
|
+
# @param value [Object] The value to parse.
|
|
185
|
+
# @param formats [Array|NilClass] A list of valid formats (see strftime). Will fallback to formats defined in Rails configuration.
|
|
186
|
+
# @param raise_errors [Boolean] Whether to raise errors in case the value couldn't be parsed with any format.
|
|
187
|
+
# @return [DateTime|NilClass] A `DateTime` if parsing succeded, `nil` otherwise.
|
|
188
|
+
def self.parse(value, formats: nil, raise_errors: false)
|
|
189
|
+
return value if [ActiveSupport::TimeWithZone, DateTime, Date, Time].include?(value.class)
|
|
190
|
+
|
|
191
|
+
formats ||= Apes::RuntimeConfiguration.timestamp_formats.values.dup
|
|
192
|
+
|
|
193
|
+
rv = catch(:valid) do
|
|
194
|
+
formats.each do |format|
|
|
195
|
+
parsed = safe_parse(value, format)
|
|
196
|
+
|
|
197
|
+
throw(:valid, parsed) if parsed
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
raise(ArgumentError, "Invalid timestamp \"#{value}\".") if !rv && raise_errors
|
|
204
|
+
rv
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Parses a timestamp without raising exceptions.
|
|
208
|
+
#
|
|
209
|
+
# @param value [String] The value to parse.
|
|
210
|
+
# @return [DateTime|NilClass] A `DateTime` if parsing succeded, `nil` otherwise.
|
|
211
|
+
def self.safe_parse(value, format)
|
|
212
|
+
DateTime.strptime(value, format)
|
|
213
|
+
rescue
|
|
214
|
+
nil
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Creates a new validator.
|
|
218
|
+
#
|
|
219
|
+
# @param options [Hash] The options for the validations.
|
|
220
|
+
# @return [Apes::Validators::TimestampValidator] A new validator.
|
|
221
|
+
def initialize(options)
|
|
222
|
+
super(options.reverse_merge(default_message: "must be a valid ISO 8601 timestamp"))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Checks if the value is valid for this validator.
|
|
226
|
+
#
|
|
227
|
+
# @param value [Object] The value to validate.
|
|
228
|
+
# @return [Boolean] `true` if the value is valid, false otherwise.
|
|
229
|
+
def check_valid?(value)
|
|
230
|
+
value.blank? || TimestampValidator.parse(value, formats: options[:formats])
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|