apes 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|