You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
6.8 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var SchemaType = require('../schematype')
  5. , CastError = SchemaType.CastError
  6. , utils = require('../utils')
  7. , Document
  8. /**
  9. * String SchemaType constructor.
  10. *
  11. * @param {String} key
  12. * @param {Object} options
  13. * @inherits SchemaType
  14. * @api private
  15. */
  16. function SchemaString (key, options) {
  17. this.enumValues = [];
  18. this.regExp = null;
  19. SchemaType.call(this, key, options, 'String');
  20. };
  21. /*!
  22. * Inherits from SchemaType.
  23. */
  24. SchemaString.prototype.__proto__ = SchemaType.prototype;
  25. /**
  26. * Adds enumeration values and a coinciding validator.
  27. *
  28. * ####Example:
  29. *
  30. * var states = 'opening open closing closed'.split(' ')
  31. * var s = new Schema({ state: { type: String, enum: states })
  32. * var M = db.model('M', s)
  33. * var m = new M({ state: 'invalid' })
  34. * m.save(function (err) {
  35. * console.error(err) // validator error
  36. * m.state = 'open'
  37. * m.save() // success
  38. * })
  39. *
  40. * @param {String} [args...] enumeration values
  41. * @return {SchemaType} this
  42. * @api public
  43. */
  44. SchemaString.prototype.enum = function () {
  45. var len = arguments.length;
  46. if (!len || undefined === arguments[0] || false === arguments[0]) {
  47. if (this.enumValidator){
  48. this.enumValidator = false;
  49. this.validators = this.validators.filter(function(v){
  50. return v[1] != 'enum';
  51. });
  52. }
  53. return this;
  54. }
  55. for (var i = 0; i < len; i++) {
  56. if (undefined !== arguments[i]) {
  57. this.enumValues.push(this.cast(arguments[i]));
  58. }
  59. }
  60. if (!this.enumValidator) {
  61. var values = this.enumValues;
  62. this.enumValidator = function(v){
  63. return undefined === v || ~values.indexOf(v);
  64. };
  65. this.validators.push([this.enumValidator, 'enum']);
  66. }
  67. return this;
  68. };
  69. /**
  70. * Adds a lowercase setter.
  71. *
  72. * ####Example:
  73. *
  74. * var s = new Schema({ email: { type: String, lowercase: true }})
  75. * var M = db.model('M', s);
  76. * var m = new M({ email: 'SomeEmail@example.COM' });
  77. * console.log(m.email) // someemail@example.com
  78. *
  79. * @api public
  80. * @return {SchemaType} this
  81. */
  82. SchemaString.prototype.lowercase = function () {
  83. return this.set(function (v, self) {
  84. if ('string' != typeof v) v = self.cast(v)
  85. if (v) return v.toLowerCase();
  86. return v;
  87. });
  88. };
  89. /**
  90. * Adds an uppercase setter.
  91. *
  92. * ####Example:
  93. *
  94. * var s = new Schema({ caps: { type: String, uppercase: true }})
  95. * var M = db.model('M', s);
  96. * var m = new M({ caps: 'an example' });
  97. * console.log(m.caps) // AN EXAMPLE
  98. *
  99. * @api public
  100. * @return {SchemaType} this
  101. */
  102. SchemaString.prototype.uppercase = function () {
  103. return this.set(function (v, self) {
  104. if ('string' != typeof v) v = self.cast(v)
  105. if (v) return v.toUpperCase();
  106. return v;
  107. });
  108. };
  109. /**
  110. * Adds a trim setter.
  111. *
  112. * The string value will be trimmed when set.
  113. *
  114. * ####Example:
  115. *
  116. * var s = new Schema({ name: { type: String, trim: true }})
  117. * var M = db.model('M', s)
  118. * var string = ' some name '
  119. * console.log(string.length) // 11
  120. * var m = new M({ name: string })
  121. * console.log(m.name.length) // 9
  122. *
  123. * @api public
  124. * @return {SchemaType} this
  125. */
  126. SchemaString.prototype.trim = function () {
  127. return this.set(function (v, self) {
  128. if ('string' != typeof v) v = self.cast(v)
  129. if (v) return v.trim();
  130. return v;
  131. });
  132. };
  133. /**
  134. * Sets a regexp validator.
  135. *
  136. * Any value that does not pass `regExp`.test(val) will fail validation.
  137. *
  138. * ####Example:
  139. *
  140. * var s = new Schema({ name: { type: String, match: /^a/ }})
  141. * var M = db.model('M', s)
  142. * var m = new M({ name: 'invalid' })
  143. * m.validate(function (err) {
  144. * console.error(err) // validation error
  145. * m.name = 'apples'
  146. * m.validate(function (err) {
  147. * assert.ok(err) // success
  148. * })
  149. * })
  150. *
  151. * @param {RegExp} regExp regular expression to test against
  152. * @return {SchemaType} this
  153. * @api public
  154. */
  155. SchemaString.prototype.match = function match (regExp) {
  156. this.validators.push([function(v){
  157. return null != v && '' !== v
  158. ? regExp.test(v)
  159. : true
  160. }, 'regexp']);
  161. return this;
  162. };
  163. /**
  164. * Check required
  165. *
  166. * @param {String|null|undefined} value
  167. * @api private
  168. */
  169. SchemaString.prototype.checkRequired = function checkRequired (value, doc) {
  170. if (SchemaType._isRef(this, value, doc, true)) {
  171. return null != value;
  172. } else {
  173. return (value instanceof String || typeof value == 'string') && value.length;
  174. }
  175. };
  176. /**
  177. * Casts to String
  178. *
  179. * @api private
  180. */
  181. SchemaString.prototype.cast = function (value, doc, init) {
  182. if (SchemaType._isRef(this, value, doc, init)) {
  183. // wait! we may need to cast this to a document
  184. if (null == value) {
  185. return value;
  186. }
  187. // lazy load
  188. Document || (Document = require('./../document'));
  189. if (value instanceof Document) {
  190. value.$__.wasPopulated = true;
  191. return value;
  192. }
  193. // setting a populated path
  194. if ('string' == typeof value) {
  195. return value;
  196. } else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
  197. throw new CastError('string', value, this.path);
  198. }
  199. // Handle the case where user directly sets a populated
  200. // path to a plain object; cast to the Model used in
  201. // the population query.
  202. var path = doc.$__fullPath(this.path);
  203. var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
  204. var pop = owner.populated(path, true);
  205. var ret = new pop.options.model(value);
  206. ret.$__.wasPopulated = true;
  207. return ret;
  208. }
  209. if (value === null) {
  210. return value;
  211. }
  212. if ('undefined' !== typeof value) {
  213. // handle documents being passed
  214. if (value._id && 'string' == typeof value._id) {
  215. return value._id;
  216. }
  217. if (value.toString) {
  218. return value.toString();
  219. }
  220. }
  221. throw new CastError('string', value, this.path);
  222. };
  223. /*!
  224. * ignore
  225. */
  226. function handleSingle (val) {
  227. return this.castForQuery(val);
  228. }
  229. function handleArray (val) {
  230. var self = this;
  231. return val.map(function (m) {
  232. return self.castForQuery(m);
  233. });
  234. }
  235. SchemaString.prototype.$conditionalHandlers = {
  236. '$ne' : handleSingle
  237. , '$in' : handleArray
  238. , '$nin': handleArray
  239. , '$gt' : handleSingle
  240. , '$lt' : handleSingle
  241. , '$gte': handleSingle
  242. , '$lte': handleSingle
  243. , '$all': handleArray
  244. , '$regex': handleSingle
  245. , '$options': handleSingle
  246. };
  247. /**
  248. * Casts contents for queries.
  249. *
  250. * @param {String} $conditional
  251. * @param {any} [val]
  252. * @api private
  253. */
  254. SchemaString.prototype.castForQuery = function ($conditional, val) {
  255. var handler;
  256. if (arguments.length === 2) {
  257. handler = this.$conditionalHandlers[$conditional];
  258. if (!handler)
  259. throw new Error("Can't use " + $conditional + " with String.");
  260. return handler.call(this, val);
  261. } else {
  262. val = $conditional;
  263. if (val instanceof RegExp) return val;
  264. return this.cast(val);
  265. }
  266. };
  267. /*!
  268. * Module exports.
  269. */
  270. module.exports = SchemaString;